diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 9a188b7..905a1c6 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -2088,6 +2088,61 @@ 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_ERROR,
+			   "CTRL: Request range - bad destination address");
+		return -1;
+	}
+
+	token = str_token(cmd, " ", &context);
+	if (!token)
+		return -1;
+
+	random_interval = atoi(token);
+
+	token = str_token(cmd, " ", &context);
+	if (!token)
+		return -1;
+
+	min_ap = atoi(token);
+
+	n_responders = 0;
+	while ((token = str_token(cmd, " ", &context))) {
+		if (n_responders == RRM_RANGE_REQ_MAX_RESPONDERS) {
+			wpa_printf(MSG_ERROR,
+				   "CTRL: Request range - too many responders");
+			return -1;
+		}
+
+		if (hwaddr_aton(token, responders + n_responders * ETH_ALEN)) {
+			wpa_printf(MSG_ERROR,
+				   "CTRL: Request range - bad responder address");
+			return -1;
+		}
+
+		n_responders++;
+	}
+
+	if (!n_responders) {
+		wpa_printf(MSG_ERROR,
+			   "CTRL: Request 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;
@@ -2461,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 b2e0de1..d46f20c 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1158,6 +1158,35 @@ static int hostapd_cli_req_lci(struct wpa_ctrl *ctrl, int argc, char *argv[])
 }
 
 
+static int hostapd_cli_req_range(struct wpa_ctrl *ctrl, int argc, char *argv[])
+{
+	char cmd[512];
+	char *pos  = cmd, *end = cmd + sizeof(cmd);
+	int i, res;
+
+	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;
+	}
+
+	res = os_snprintf(pos, end - pos, "%s", "REQ_RANGE ");
+	if (os_snprintf_error(end - pos, res))
+		return -1;
+	pos += res;
+
+	for (i = 0; i < argc; i++) {
+		res = os_snprintf(pos, end - pos, " %s", argv[i]);
+		if (os_snprintf_error(end - pos, res)) {
+			printf("Too long REQ_RANGE command");
+			return -1;
+		}
+		pos += res;
+	}
+
+	return wpa_ctrl_command(ctrl, cmd);
+}
+
+
 static int hostapd_cli_cmd_erp_flush(struct wpa_ctrl *ctrl, int argc,
 				     char *argv[])
 {
@@ -1269,6 +1298,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_req_lci },
+	{ "req_range", hostapd_cli_req_range },
 	{ NULL, NULL }
 };
 
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index c3d7807..6961d37 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -301,6 +301,9 @@ struct hostapd_data {
 
 	u8 lci_req_token;
 	int lci_req_active;
+
+	u8 range_req_token;
+	int range_req_active;
 };
 
 
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index e3084e3..985b4a3 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -13,16 +13,17 @@
 #include "neighbor_db.h"
 
 
-static struct hostapd_neighbor_entry
-*hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid,
-		      struct wpa_ssid_value *ssid)
+struct hostapd_neighbor_entry *hostapd_neighbor_get(struct hostapd_data *hapd,
+						    const u8 *bssid,
+						    struct wpa_ssid_value *ssid)
 {
 	struct hostapd_neighbor_entry *nr;
 
 	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 35084a7..6c4139f 100644
--- a/src/ap/neighbor_db.h
+++ b/src/ap/neighbor_db.h
@@ -10,6 +10,10 @@
 #ifndef NEIGHBOR_DB_H
 #define NEIGHBOR_DB_H
 
+struct hostapd_neighbor_entry *
+hostapd_neighbor_get(struct hostapd_data *hapd,
+		     const u8 *bssid,
+		     struct wpa_ssid_value *ssid);
 int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid,
 			 struct wpa_ssid_value *ssid, struct wpabuf *nr,
 			 struct wpabuf *lci, struct wpabuf *civic);
diff --git a/src/ap/rrm.c b/src/ap/rrm.c
index 61c5931..2cef48e 100644
--- a/src/ap/rrm.c
+++ b/src/ap/rrm.c
@@ -7,6 +7,7 @@
  * See README for more details.
  */
 
+#include <limits.h>
 #include "utils/includes.h"
 #include "utils/common.h"
 #include "hostapd.h"
@@ -28,6 +29,15 @@ static void hoastapd_lci_rep_timeout_handler(void *eloop_data, void *user_ctx)
 }
 
 
+static void hoastapd_range_rep_timeout_handler(void *eloop_data, void *user_ctx)
+{
+	struct hostapd_data *hapd = eloop_data;
+
+	wpa_printf(MSG_DEBUG, "RRM: range request timed out");
+	hapd->range_req_active = 0;
+}
+
+
 static void hostapd_handle_lci_report(struct hostapd_data *hapd, u8 token,
 				      const u8 *pos, size_t len)
 {
@@ -43,6 +53,22 @@ static void hostapd_handle_lci_report(struct hostapd_data *hapd, u8 token,
 }
 
 
+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 %d",
+			   token);
+	} else {
+		hapd->range_req_active = 0;
+		eloop_cancel_timeout(hoastapd_range_rep_timeout_handler, hapd,
+				     NULL);
+		wpa_printf(MSG_DEBUG, "Range report token %d len %zu", token,
+			   len);
+	}
+}
+
+
 static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd,
 					     const u8 *buf, size_t len)
 {
@@ -65,6 +91,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",
@@ -384,10 +413,143 @@ int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr)
 }
 
 
+int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr,
+			   int random_interval, int 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 %d min ap %d n_responders %d", MAC2STR(addr),
+		   random_interval, min_ap, n_responders);
+
+	if (min_ap <= 0 || min_ap > WLAN_RRM_RANGE_REQ_MAX_MIN_AP ||
+	    (unsigned int)min_ap > n_responders) {
+		wpa_printf(MSG_ERROR, "Request range: wrong min AP count");
+		return -1;
+	}
+
+	if (random_interval >= USHRT_MAX) {
+		wpa_printf(MSG_ERROR,
+			   "Request range: too big randomization interval");
+		return -1;
+	}
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		wpa_printf(MSG_ERROR,
+			   "Request range: destination address isn't in station list");
+		return -1;
+	}
+
+	if (!(sta->flags & WLAN_STA_AUTHORIZED)) {
+		wpa_printf(MSG_ERROR,
+			   "Request range: destination address isn't connected");
+		return -1;
+	}
+
+	if (!(sta->rrm_enabled_capa[4] & WLAN_RRM_CAPS_RANGE_REPORT)) {
+		wpa_printf(MSG_ERROR,
+			   "Request range: receiving station doesn't support 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,
+				       hoastapd_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++;
+
+	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);
+	/* Num of repetitions */
+	wpabuf_put_le16(buf, 0);
+
+	wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
+	/* Len will be set later */
+	len = wpabuf_mhead_u8(buf);
+	wpabuf_put_u8(buf, 0);
+
+	/* Measurement token */
+	wpabuf_put_u8(buf, 1);
+	/* Parallel and enable bits are 0, duration, request and report are
+	 * reserved
+	 */
+	wpabuf_put_u8(buf, 0);
+	wpabuf_put_u8(buf, MEASURE_TYPE_FTM_RANGE);
+
+	wpabuf_put_le16(buf, random_interval);
+	wpabuf_put_u8(buf, min_ap);
+
+	/* Taking the neighbor report part of the range request from neighbor
+	 * DB (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_ERROR, "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,
+			       hoastapd_range_rep_timeout_handler,
+			       hapd, NULL);
+
+	return 0;
+}
+
+
 void hostapd_clean_rrm(struct hostapd_data *hapd)
 {
 	hostpad_free_neighbor_db(hapd);
 	eloop_cancel_timeout(hoastapd_lci_rep_timeout_handler, hapd,
 			     NULL);
 	hapd->lci_req_active = 0;
+	eloop_cancel_timeout(hoastapd_range_rep_timeout_handler, hapd,
+			     NULL);
+	hapd->range_req_active = 0;
 }
diff --git a/src/ap/rrm.h b/src/ap/rrm.h
index 08ae438..ac63777 100644
--- a/src/ap/rrm.h
+++ b/src/ap/rrm.h
@@ -10,10 +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,
+			   int random_interval, int 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 5f548dd..87addaf 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -368,6 +368,13 @@
 #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_RANGE_REPORT BIT(2)
+
+/* 802.11 MC D4.3 spec - 8.4.2.20.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
