Parcourir la source

Add driver API functionality for off-channel Action frames

This adds new commands and events for allowing off-channel Action
frame exchanges to be requested. This functionality is not yet used
and is only fully supported by driver_test.c at this point.
driver_nl80211.c has support for the remain-on-channel commands, but
the Action frame TX/RX part is still pending review for the kernel
code and as such, is not yet included here.
Jouni Malinen il y a 15 ans
Parent
commit
55777702cd

+ 134 - 0
src/drivers/driver.h

@@ -1519,6 +1519,27 @@ struct wpa_driver_ops {
 	 */
 	int (*set_wds_sta)(void *priv, const u8 *addr, int aid, int val);
 
+	/**
+	 * send_action - Transmit an Action frame
+	 * @priv: Private driver interface data
+	 * @freq: Frequency (in MHz) of the channel
+	 * @dst: Destination MAC address
+	 * @src: Source MAC address
+	 * @data: Frame body
+	 * @data_len: data length in octets
+	 * Returns: 0 on success, -1 on failure
+	 *
+	 * This command can be used to request the driver to transmit an action
+	 * frame to the specified destination. If a remain-on-channel duration
+	 * is in progress, the frame is transmitted on that channel. Otherwise,
+	 * the frame is transmitted on the current operational channel if in
+	 * associated state in station mode or if operating as an AP. If none
+	 * of these conditions is in effect, send_action() cannot be used.
+	 */
+	int (*send_action)(void *priv, unsigned int freq,
+			   const u8 *dst, const u8 *src,
+			   const u8 *data, size_t data_len);
+
 	/**
 	 * alloc_interface_addr - Allocate a virtual interface address
 	 * @priv: Private driver interface data
@@ -1549,6 +1570,44 @@ struct wpa_driver_ops {
 	 */
 	void (*release_interface_addr)(void *priv, const u8 *addr);
 
+	/**
+	 * remain_on_channel - Remain awake on a channel
+	 * @priv: Private driver interface data
+	 * @freq: Frequency (in MHz) of the channel
+	 * @duration: Duration in milliseconds
+	 * Returns: 0 on success, -1 on failure
+	 *
+	 * This command is used to request the driver to remain awake on the
+	 * specified channel for the specified duration and report received
+	 * Action frames with EVENT_RX_ACTION events. Optionally, received
+	 * Probe Request frames may also be requested to be reported by calling
+	 * probe_req_report(). These will be reported with EVENT_RX_PROBE_REQ.
+	 *
+	 * The driver may not be at the requested channel when this function
+	 * returns, i.e., the return code is only indicating whether the
+	 * request was accepted. The caller will need to wait until the
+	 * EVENT_REMAIN_ON_CHANNEL event indicates that the driver has
+	 * completed the channel change. This may take some time due to other
+	 * need for the radio and the caller should be prepared to timing out
+	 * its wait since there are no guarantees on when this request can be
+	 * executed.
+	 */
+	int (*remain_on_channel)(void *priv, unsigned int freq,
+				 unsigned int duration);
+
+	/**
+	 * cancel_remain_on_channel - Cancel remain-on-channel operation
+	 * @priv: Private driver interface data
+	 *
+	 * This command can be used to cancel a remain-on-channel operation
+	 * before its originally requested duration has passed. This could be
+	 * used, e.g., when remain_on_channel() is used to request extra time
+	 * to receive a response to an Action frame and the response is
+	 * received when there is still unneeded time remaining on the
+	 * remain-on-channel operation.
+	 */
+	int (*cancel_remain_on_channel)(void *priv);
+
 	/**
 	 * probe_req_report - Request Probe Request frames to be indicated
 	 * @priv: Private driver interface data
@@ -1766,6 +1825,34 @@ enum wpa_event_type {
 	 */
 	EVENT_RX_MGMT,
 
+	/**
+	 * EVENT_RX_ACTION - Action frame received
+	 *
+	 * This event is used to indicate when an Action frame has been
+	 * received. Information about the received frame is included in
+	 * union wpa_event_data::rx_action.
+	 */
+	EVENT_RX_ACTION,
+
+	/**
+	 * EVENT_REMAIN_ON_CHANNEL - Remain-on-channel duration started
+	 *
+	 * This event is used to indicate when the driver has started the
+	 * requested remain-on-channel duration. Information about the
+	 * operation is included in union wpa_event_data::remain_on_channel.
+	 */
+	EVENT_REMAIN_ON_CHANNEL,
+
+	/**
+	 * EVENT_CANCEL_REMAIN_ON_CHANNEL - Remain-on-channel timed out
+	 *
+	 * This event is used to indicate when the driver has completed
+	 * remain-on-channel duration, i.e., may noot be available on the
+	 * requested channel anymore. Information about the
+	 * operation is included in union wpa_event_data::remain_on_channel.
+	 */
+	EVENT_CANCEL_REMAIN_ON_CHANNEL,
+
 	/**
 	 * EVENT_MLME_RX - Report reception of frame for MLME (test use only)
 	 *
@@ -2011,6 +2098,53 @@ union wpa_event_data {
 		u32 ssi_signal;
 	} rx_mgmt;
 
+	/**
+	 * struct rx_action - Data for EVENT_RX_ACTION events
+	 */
+	struct rx_action {
+		/**
+		 * sa - Source address of the received Action frame
+		 */
+		const u8 *sa;
+
+		/**
+		 * category - Action frame category
+		 */
+		u8 category;
+
+		/**
+		 * data - Action frame body after category field
+		 */
+		const u8 *data;
+
+		/**
+		 * len - Length of data in octets
+		 */
+		size_t len;
+
+		/**
+		 * freq - Frequency (in MHz) on which the frame was received
+		 */
+		int freq;
+	} rx_action;
+
+	/**
+	 * struct remain_on_channel - Data for EVENT_REMAIN_ON_CHANNEL events
+	 *
+	 * This is also used with EVENT_CANCEL_REMAIN_ON_CHANNEL events.
+	 */
+	struct remain_on_channel {
+		/**
+		 * freq - Channel frequency in MHz
+		 */
+		unsigned int freq;
+
+		/**
+		 * duration - Duration to remain on the channel in milliseconds
+		 */
+		unsigned int duration;
+	} remain_on_channel;
+
 	/**
 	 * struct scan_info - Optional data for EVENT_SCAN_RESULTS events
 	 * @aborted: Whether the scan was aborted

+ 3 - 0
src/drivers/driver_ndis.c

@@ -3246,7 +3246,10 @@ const struct wpa_driver_ops wpa_driver_ndis_ops = {
 	NULL /* set_ap_wps_ie */,
 	NULL /* set_supp_port */,
 	NULL /* set_wds_sta */,
+	NULL /* send_action */,
 	NULL /* alloc_interface_addr */,
 	NULL /* release_interface_addr */,
+	NULL /* remain_on_channel */,
+	NULL /* cancel_remain_on_channel */,
 	NULL /* probe_req_report */
 };

+ 143 - 0
src/drivers/driver_nl80211.c

@@ -98,6 +98,9 @@ struct wpa_driver_nl80211_data {
 	int probe_req_report;
 
 	unsigned int beacon_set:1;
+	unsigned int pending_remain_on_chan:1;
+
+	u64 remain_on_chan_cookie;
 
 #ifdef HOSTAPD
 	int eapol_sock; /* socket for EAPOL frames */
@@ -709,6 +712,53 @@ static void mlme_event_join_ibss(struct wpa_driver_nl80211_data *drv,
 }
 
 
+static void mlme_event_remain_on_channel(struct wpa_driver_nl80211_data *drv,
+					 int cancel_event, struct nlattr *tb[])
+{
+	unsigned int freq, chan_type, duration;
+	union wpa_event_data data;
+	u64 cookie;
+
+	if (tb[NL80211_ATTR_WIPHY_FREQ])
+		freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
+	else
+		freq = 0;
+
+	if (tb[NL80211_ATTR_WIPHY_CHANNEL_TYPE])
+		chan_type = nla_get_u32(tb[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
+	else
+		chan_type = 0;
+
+	if (tb[NL80211_ATTR_DURATION])
+		duration = nla_get_u32(tb[NL80211_ATTR_DURATION]);
+	else
+		duration = 0;
+
+	if (tb[NL80211_ATTR_COOKIE])
+		cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]);
+	else
+		cookie = 0;
+
+	wpa_printf(MSG_DEBUG, "nl80211: Remain-on-channel event (cancel=%d "
+		   "freq=%u channel_type=%u duration=%u cookie=0x%llx (%s))",
+		   cancel_event, freq, chan_type, duration,
+		   (long long unsigned int) cookie,
+		   cookie == drv->remain_on_chan_cookie ? "match" : "unknown");
+
+	if (cookie != drv->remain_on_chan_cookie)
+		return; /* not for us */
+
+	drv->pending_remain_on_chan = !cancel_event;
+
+	os_memset(&data, 0, sizeof(data));
+	data.remain_on_channel.freq = freq;
+	data.remain_on_channel.duration = duration;
+	wpa_supplicant_event(drv->ctx, cancel_event ?
+			     EVENT_CANCEL_REMAIN_ON_CHANNEL :
+			     EVENT_REMAIN_ON_CHANNEL, &data);
+}
+
+
 static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
 			    struct nlattr *tb[])
 {
@@ -831,6 +881,12 @@ static int process_event(struct nl_msg *msg, void *arg)
 	case NL80211_CMD_JOIN_IBSS:
 		mlme_event_join_ibss(drv, tb);
 		break;
+	case NL80211_CMD_REMAIN_ON_CHANNEL:
+		mlme_event_remain_on_channel(drv, 0, tb);
+		break;
+	case NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL:
+		mlme_event_remain_on_channel(drv, 1, tb);
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "nl80211: Ignored unknown event "
 			   "(cmd=%d)", gnlh->cmd);
@@ -4415,6 +4471,90 @@ static int wpa_driver_nl80211_if_remove(void *priv,
 }
 
 
+static int cookie_handler(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *tb[NL80211_ATTR_MAX + 1];
+	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+	u64 *cookie = arg;
+	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+		  genlmsg_attrlen(gnlh, 0), NULL);
+	if (tb[NL80211_ATTR_COOKIE])
+		*cookie = nla_get_u64(tb[NL80211_ATTR_COOKIE]);
+	return NL_SKIP;
+}
+
+
+static int wpa_driver_nl80211_remain_on_channel(void *priv, unsigned int freq,
+						unsigned int duration)
+{
+	struct wpa_driver_nl80211_data *drv = priv;
+	struct nl_msg *msg;
+	int ret;
+	u64 cookie;
+
+	msg = nlmsg_alloc();
+	if (!msg)
+		return -1;
+
+	genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, 0,
+		    NL80211_CMD_REMAIN_ON_CHANNEL, 0);
+
+	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
+	NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq);
+	NLA_PUT_U32(msg, NL80211_ATTR_DURATION, duration);
+
+	cookie = 0;
+	ret = send_and_recv_msgs(drv, msg, cookie_handler, &cookie);
+	if (ret == 0) {
+		wpa_printf(MSG_DEBUG, "nl80211: Remain-on-channel cookie "
+			   "0x%llx for freq=%u MHz duration=%u",
+			   (long long unsigned int) cookie, freq, duration);
+		drv->remain_on_chan_cookie = cookie;
+		return 0;
+	}
+	wpa_printf(MSG_DEBUG, "nl80211: Failed to request remain-on-channel "
+		   "(freq=%d): %d (%s)", freq, ret, strerror(-ret));
+nla_put_failure:
+	return -1;
+}
+
+
+static int wpa_driver_nl80211_cancel_remain_on_channel(void *priv)
+{
+	struct wpa_driver_nl80211_data *drv = priv;
+	struct nl_msg *msg;
+	int ret;
+
+	if (!drv->pending_remain_on_chan) {
+		wpa_printf(MSG_DEBUG, "nl80211: No pending remain-on-channel "
+			   "to cancel");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "nl80211: Cancel remain-on-channel with cookie "
+		   "0x%llx",
+		   (long long unsigned int) drv->remain_on_chan_cookie);
+
+	msg = nlmsg_alloc();
+	if (!msg)
+		return -1;
+
+	genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, 0,
+		    NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL, 0);
+
+	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
+	NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, drv->remain_on_chan_cookie);
+
+	ret = send_and_recv_msgs(drv, msg, NULL, NULL);
+	if (ret == 0)
+		return 0;
+	wpa_printf(MSG_DEBUG, "nl80211: Failed to cancel remain-on-channel: "
+		   "%d (%s)", ret, strerror(-ret));
+nla_put_failure:
+	return -1;
+}
+
+
 static void wpa_driver_nl80211_probe_req_report_timeout(void *eloop_ctx,
 							void *timeout_ctx)
 {
@@ -4543,6 +4683,9 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.set_sta_vlan = i802_set_sta_vlan,
 	.set_wds_sta = i802_set_wds_sta,
 #endif /* HOSTAPD */
+	.remain_on_channel = wpa_driver_nl80211_remain_on_channel,
+	.cancel_remain_on_channel =
+	wpa_driver_nl80211_cancel_remain_on_channel,
 	.probe_req_report = wpa_driver_nl80211_probe_req_report,
 	.alloc_interface_addr = wpa_driver_nl80211_alloc_interface_addr,
 	.release_interface_addr = wpa_driver_nl80211_release_interface_addr,

+ 120 - 0
src/drivers/driver_test.c

@@ -104,6 +104,10 @@ struct wpa_driver_test_data {
 	int alloc_iface_idx;
 
 	int probe_req_report;
+	unsigned int remain_on_channel_freq;
+	unsigned int remain_on_channel_duration;
+
+	int current_freq;
 };
 
 
@@ -112,6 +116,7 @@ static int wpa_driver_test_attach(struct wpa_driver_test_data *drv,
 				  const char *dir, int ap);
 static void wpa_driver_test_close_test_socket(
 	struct wpa_driver_test_data *drv);
+static void test_remain_on_channel_timeout(void *eloop_ctx, void *timeout_ctx);
 
 
 static void test_driver_free_bss(struct test_driver_bss *bss)
@@ -1986,6 +1991,7 @@ static void wpa_driver_test_deinit(void *priv)
 	wpa_driver_test_close_test_socket(drv);
 	eloop_cancel_timeout(wpa_driver_test_scan_timeout, drv, drv->ctx);
 	eloop_cancel_timeout(wpa_driver_test_poll, drv, NULL);
+	eloop_cancel_timeout(test_remain_on_channel_timeout, drv, NULL);
 	os_free(drv->test_dir);
 	for (i = 0; i < MAX_SCAN_RESULTS; i++)
 		os_free(drv->scanres[i]);
@@ -2300,8 +2306,10 @@ static int wpa_driver_test_set_channel(void *priv,
 				       enum hostapd_hw_mode phymode,
 				       int chan, int freq)
 {
+	struct wpa_driver_test_data *drv = priv;
 	wpa_printf(MSG_DEBUG, "%s: phymode=%d chan=%d freq=%d",
 		   __func__, phymode, chan, freq);
+	drv->current_freq = freq;
 	return 0;
 }
 
@@ -2464,6 +2472,56 @@ fail:
 }
 
 
+static int wpa_driver_test_set_freq(void *priv,
+				    struct hostapd_freq_params *freq)
+{
+	struct wpa_driver_test_data *drv = priv;
+	wpa_printf(MSG_DEBUG, "test: set_freq %u MHz", freq->freq);
+	drv->current_freq = freq->freq;
+	return 0;
+}
+
+
+static int wpa_driver_test_send_action(void *priv, unsigned int freq,
+				       const u8 *dst, const u8 *src,
+				       const u8 *data, size_t data_len)
+{
+	struct wpa_driver_test_data *drv = priv;
+	int ret = -1;
+	u8 *buf;
+	struct ieee80211_hdr *hdr;
+
+	wpa_printf(MSG_DEBUG, "test: Send Action frame");
+
+	if ((drv->remain_on_channel_freq &&
+	     freq != drv->remain_on_channel_freq) ||
+	    (drv->remain_on_channel_freq == 0 &&
+	     freq != (unsigned int) drv->current_freq)) {
+		wpa_printf(MSG_DEBUG, "test: Reject Action frame TX on "
+			   "unexpected channel: freq=%u MHz (current_freq=%u "
+			   "MHz, remain-on-channel freq=%u MHz)",
+			   freq, drv->current_freq,
+			   drv->remain_on_channel_freq);
+		return -1;
+	}
+
+	buf = os_zalloc(24 + data_len);
+	if (buf == NULL)
+		return ret;
+	os_memcpy(buf + 24, data, data_len);
+	hdr = (struct ieee80211_hdr *) buf;
+	hdr->frame_control =
+		IEEE80211_FC(WLAN_FC_TYPE_MGMT, WLAN_FC_STYPE_ACTION);
+	os_memcpy(hdr->addr1, dst, ETH_ALEN);
+	os_memcpy(hdr->addr2, src, ETH_ALEN);
+	os_memcpy(hdr->addr3, "\xff\xff\xff\xff\xff\xff", ETH_ALEN);
+
+	ret = wpa_driver_test_send_mlme(priv, buf, 24 + data_len);
+	os_free(buf);
+	return ret;
+}
+
+
 static int wpa_driver_test_alloc_interface_addr(void *priv, u8 *addr)
 {
 	struct wpa_driver_test_data *drv = priv;
@@ -2482,6 +2540,64 @@ static void wpa_driver_test_release_interface_addr(void *priv, const u8 *addr)
 }
 
 
+static void test_remain_on_channel_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_driver_test_data *drv = eloop_ctx;
+	union wpa_event_data data;
+
+	wpa_printf(MSG_DEBUG, "test: Remain-on-channel timeout");
+
+	os_memset(&data, 0, sizeof(data));
+	data.remain_on_channel.freq = drv->remain_on_channel_freq;
+	data.remain_on_channel.duration = drv->remain_on_channel_duration;
+	wpa_supplicant_event(drv->ctx, EVENT_CANCEL_REMAIN_ON_CHANNEL, &data);
+
+	drv->remain_on_channel_freq = 0;
+}
+
+
+static int wpa_driver_test_remain_on_channel(void *priv, unsigned int freq,
+					     unsigned int duration)
+{
+	struct wpa_driver_test_data *drv = priv;
+	union wpa_event_data data;
+
+	wpa_printf(MSG_DEBUG, "%s(freq=%u, duration=%u)",
+		   __func__, freq, duration);
+	if (drv->remain_on_channel_freq &&
+	    drv->remain_on_channel_freq != freq) {
+		wpa_printf(MSG_DEBUG, "test: Refuse concurrent "
+			   "remain_on_channel request");
+		return -1;
+	}
+
+	drv->remain_on_channel_freq = freq;
+	drv->remain_on_channel_duration = duration;
+	eloop_cancel_timeout(test_remain_on_channel_timeout, drv, NULL);
+	eloop_register_timeout(duration / 1000, (duration % 1000) * 1000,
+			       test_remain_on_channel_timeout, drv, NULL);
+
+	os_memset(&data, 0, sizeof(data));
+	data.remain_on_channel.freq = freq;
+	data.remain_on_channel.duration = duration;
+	wpa_supplicant_event(drv->ctx, EVENT_REMAIN_ON_CHANNEL, &data);
+
+	return 0;
+}
+
+
+static int wpa_driver_test_cancel_remain_on_channel(void *priv)
+{
+	struct wpa_driver_test_data *drv = priv;
+	wpa_printf(MSG_DEBUG, "%s", __func__);
+	if (!drv->remain_on_channel_freq)
+		return -1;
+	drv->remain_on_channel_freq = 0;
+	eloop_cancel_timeout(test_remain_on_channel_timeout, drv, NULL);
+	return 0;
+}
+
+
 static int wpa_driver_test_probe_req_report(void *priv, int report)
 {
 	struct wpa_driver_test_data *drv = priv;
@@ -2534,7 +2650,11 @@ const struct wpa_driver_ops wpa_driver_test_ops = {
 	.init2 = wpa_driver_test_init2,
 	.get_interfaces = wpa_driver_test_get_interfaces,
 	.scan2 = wpa_driver_test_scan,
+	.set_freq = wpa_driver_test_set_freq,
+	.send_action = wpa_driver_test_send_action,
 	.alloc_interface_addr = wpa_driver_test_alloc_interface_addr,
 	.release_interface_addr = wpa_driver_test_release_interface_addr,
+	.remain_on_channel = wpa_driver_test_remain_on_channel,
+	.cancel_remain_on_channel = wpa_driver_test_cancel_remain_on_channel,
 	.probe_req_report = wpa_driver_test_probe_req_report,
 };

+ 30 - 0
wpa_supplicant/driver_i.h

@@ -383,6 +383,17 @@ static inline int wpa_drv_set_supp_port(struct wpa_supplicant *wpa_s,
 	return 0;
 }
 
+static inline int wpa_drv_send_action(struct wpa_supplicant *wpa_s,
+				      unsigned int freq,
+				      const u8 *dst, const u8 *src,
+				      const u8 *data, size_t data_len)
+{
+	if (wpa_s->driver->send_action)
+		return wpa_s->driver->send_action(wpa_s->drv_priv, freq,
+						  dst, src, data, data_len);
+	return -1;
+}
+
 static inline int wpa_drv_alloc_interface_addr(struct wpa_supplicant *wpa_s,
 					       u8 *addr)
 {
@@ -399,6 +410,25 @@ static inline void wpa_drv_release_interface_addr(struct wpa_supplicant *wpa_s,
 		wpa_s->driver->release_interface_addr(wpa_s->drv_priv, addr);
 }
 
+static inline int wpa_drv_remain_on_channel(struct wpa_supplicant *wpa_s,
+					    unsigned int freq,
+					    unsigned int duration)
+{
+	if (wpa_s->driver->remain_on_channel)
+		return wpa_s->driver->remain_on_channel(wpa_s->drv_priv, freq,
+							duration);
+	return -1;
+}
+
+static inline int wpa_drv_cancel_remain_on_channel(
+	struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->driver->cancel_remain_on_channel)
+		return wpa_s->driver->cancel_remain_on_channel(
+			wpa_s->drv_priv);
+	return -1;
+}
+
 static inline int wpa_drv_probe_req_report(struct wpa_supplicant *wpa_s,
 					   int report)
 {