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.
This commit is contained in:
Jouni Malinen 2010-01-03 13:57:51 +02:00 committed by Jouni Malinen
parent d7c53e432b
commit 55777702cd
5 changed files with 430 additions and 0 deletions

View file

@ -1519,6 +1519,27 @@ struct wpa_driver_ops {
*/ */
int (*set_wds_sta)(void *priv, const u8 *addr, int aid, int val); 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 * alloc_interface_addr - Allocate a virtual interface address
* @priv: Private driver interface data * @priv: Private driver interface data
@ -1549,6 +1570,44 @@ struct wpa_driver_ops {
*/ */
void (*release_interface_addr)(void *priv, const u8 *addr); 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 * probe_req_report - Request Probe Request frames to be indicated
* @priv: Private driver interface data * @priv: Private driver interface data
@ -1766,6 +1825,34 @@ enum wpa_event_type {
*/ */
EVENT_RX_MGMT, 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) * EVENT_MLME_RX - Report reception of frame for MLME (test use only)
* *
@ -2011,6 +2098,53 @@ union wpa_event_data {
u32 ssi_signal; u32 ssi_signal;
} rx_mgmt; } 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 * struct scan_info - Optional data for EVENT_SCAN_RESULTS events
* @aborted: Whether the scan was aborted * @aborted: Whether the scan was aborted

View file

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

View file

@ -98,6 +98,9 @@ struct wpa_driver_nl80211_data {
int probe_req_report; int probe_req_report;
unsigned int beacon_set:1; unsigned int beacon_set:1;
unsigned int pending_remain_on_chan:1;
u64 remain_on_chan_cookie;
#ifdef HOSTAPD #ifdef HOSTAPD
int eapol_sock; /* socket for EAPOL frames */ 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, static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
struct nlattr *tb[]) struct nlattr *tb[])
{ {
@ -831,6 +881,12 @@ static int process_event(struct nl_msg *msg, void *arg)
case NL80211_CMD_JOIN_IBSS: case NL80211_CMD_JOIN_IBSS:
mlme_event_join_ibss(drv, tb); mlme_event_join_ibss(drv, tb);
break; 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: default:
wpa_printf(MSG_DEBUG, "nl80211: Ignored unknown event " wpa_printf(MSG_DEBUG, "nl80211: Ignored unknown event "
"(cmd=%d)", gnlh->cmd); "(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, static void wpa_driver_nl80211_probe_req_report_timeout(void *eloop_ctx,
void *timeout_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_sta_vlan = i802_set_sta_vlan,
.set_wds_sta = i802_set_wds_sta, .set_wds_sta = i802_set_wds_sta,
#endif /* HOSTAPD */ #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, .probe_req_report = wpa_driver_nl80211_probe_req_report,
.alloc_interface_addr = wpa_driver_nl80211_alloc_interface_addr, .alloc_interface_addr = wpa_driver_nl80211_alloc_interface_addr,
.release_interface_addr = wpa_driver_nl80211_release_interface_addr, .release_interface_addr = wpa_driver_nl80211_release_interface_addr,

View file

@ -104,6 +104,10 @@ struct wpa_driver_test_data {
int alloc_iface_idx; int alloc_iface_idx;
int probe_req_report; 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); const char *dir, int ap);
static void wpa_driver_test_close_test_socket( static void wpa_driver_test_close_test_socket(
struct wpa_driver_test_data *drv); 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) 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); wpa_driver_test_close_test_socket(drv);
eloop_cancel_timeout(wpa_driver_test_scan_timeout, drv, drv->ctx); eloop_cancel_timeout(wpa_driver_test_scan_timeout, drv, drv->ctx);
eloop_cancel_timeout(wpa_driver_test_poll, drv, NULL); eloop_cancel_timeout(wpa_driver_test_poll, drv, NULL);
eloop_cancel_timeout(test_remain_on_channel_timeout, drv, NULL);
os_free(drv->test_dir); os_free(drv->test_dir);
for (i = 0; i < MAX_SCAN_RESULTS; i++) for (i = 0; i < MAX_SCAN_RESULTS; i++)
os_free(drv->scanres[i]); os_free(drv->scanres[i]);
@ -2300,8 +2306,10 @@ static int wpa_driver_test_set_channel(void *priv,
enum hostapd_hw_mode phymode, enum hostapd_hw_mode phymode,
int chan, int freq) int chan, int freq)
{ {
struct wpa_driver_test_data *drv = priv;
wpa_printf(MSG_DEBUG, "%s: phymode=%d chan=%d freq=%d", wpa_printf(MSG_DEBUG, "%s: phymode=%d chan=%d freq=%d",
__func__, phymode, chan, freq); __func__, phymode, chan, freq);
drv->current_freq = freq;
return 0; 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) static int wpa_driver_test_alloc_interface_addr(void *priv, u8 *addr)
{ {
struct wpa_driver_test_data *drv = priv; 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) static int wpa_driver_test_probe_req_report(void *priv, int report)
{ {
struct wpa_driver_test_data *drv = priv; 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, .init2 = wpa_driver_test_init2,
.get_interfaces = wpa_driver_test_get_interfaces, .get_interfaces = wpa_driver_test_get_interfaces,
.scan2 = wpa_driver_test_scan, .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, .alloc_interface_addr = wpa_driver_test_alloc_interface_addr,
.release_interface_addr = wpa_driver_test_release_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, .probe_req_report = wpa_driver_test_probe_req_report,
}; };

View file

@ -383,6 +383,17 @@ static inline int wpa_drv_set_supp_port(struct wpa_supplicant *wpa_s,
return 0; 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, static inline int wpa_drv_alloc_interface_addr(struct wpa_supplicant *wpa_s,
u8 *addr) 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); 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, static inline int wpa_drv_probe_req_report(struct wpa_supplicant *wpa_s,
int report) int report)
{ {