Add driver API functionality for off-channel Action frames
authorJouni Malinen <jouni.malinen@atheros.com>
Sun, 3 Jan 2010 11:57:51 +0000 (13:57 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 3 Jan 2010 11:57:51 +0000 (13:57 +0200)
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.

src/drivers/driver.h
src/drivers/driver_ndis.c
src/drivers/driver_nl80211.c
src/drivers/driver_test.c
wpa_supplicant/driver_i.h

index 5fb936a..da17e55 100644 (file)
@@ -1520,6 +1520,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
         * @addr: Buffer for returning the address
@@ -1550,6 +1571,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
         * @report: Whether to report received Probe Request frames
@@ -1767,6 +1826,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)
         *
         * This event is used only by driver_test.c and userspace MLME.
@@ -2012,6 +2099,53 @@ union wpa_event_data {
        } 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
         * @freqs: Scanned frequencies in MHz (%NULL = all channels scanned)
index 0789e08..e77a2d3 100644 (file)
@@ -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 */
 };
index fc3e974..24e5b5c 100644 (file)
@@ -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,
index 5596f20..77a7c91 100644 (file)
@@ -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,
 };
index 4a701f1..2f5b134 100644 (file)
@@ -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)
 {