nl80211: Add support for off-channel Action TX/RX commands

The kernel side support for this was just added into
wireless-testing.git. This commit adds the driver wrapper code needed
to allow wpa_supplicant to use the new functionality.
This commit is contained in:
Jouni Malinen 2010-02-16 19:41:49 +02:00 committed by Jouni Malinen
parent b7a2b0b68c
commit 58f6fbe05c

View file

@ -4,7 +4,7 @@
* Copyright (c) 2003-2004, Instant802 Networks, Inc.
* Copyright (c) 2005-2006, Devicescape Software, Inc.
* Copyright (c) 2007, Johannes Berg <johannes@sipsolutions.net>
* Copyright (c) 2009, Atheros Communications
* Copyright (c) 2009-2010, Atheros Communications
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@ -101,10 +101,12 @@ struct wpa_driver_nl80211_data {
unsigned int beacon_set:1;
unsigned int pending_remain_on_chan:1;
unsigned int pending_send_action:1;
unsigned int added_bridge:1;
unsigned int added_if_into_bridge:1;
u64 remain_on_chan_cookie;
u64 send_action_cookie;
#ifdef HOSTAPD
int eapol_sock; /* socket for EAPOL frames */
@ -178,8 +180,8 @@ static int no_seq_check(struct nl_msg *msg, void *arg)
}
static int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
struct nl_msg *msg,
static int send_and_recv(struct wpa_driver_nl80211_data *drv,
struct nl_handle *nl_handle, struct nl_msg *msg,
int (*valid_handler)(struct nl_msg *, void *),
void *valid_data)
{
@ -190,7 +192,7 @@ static int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
if (!cb)
goto out;
err = nl_send_auto_complete(drv->nl_handle, msg);
err = nl_send_auto_complete(nl_handle, msg);
if (err < 0)
goto out;
@ -205,7 +207,7 @@ static int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
valid_handler, valid_data);
while (err > 0)
nl_recvmsgs(drv->nl_handle, cb);
nl_recvmsgs(nl_handle, cb);
out:
nl_cb_put(cb);
nlmsg_free(msg);
@ -213,6 +215,16 @@ static int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
}
static int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
struct nl_msg *msg,
int (*valid_handler)(struct nl_msg *, void *),
void *valid_data)
{
return send_and_recv(drv, drv->nl_handle, msg, valid_handler,
valid_data);
}
struct family_data {
const char *group;
int id;
@ -581,9 +593,74 @@ static void mlme_timeout_event(struct wpa_driver_nl80211_data *drv,
}
static void mlme_event_action(struct wpa_driver_nl80211_data *drv,
struct nlattr *freq, const u8 *frame, size_t len)
{
const struct ieee80211_mgmt *mgmt;
union wpa_event_data event;
u16 fc, stype;
mgmt = (const struct ieee80211_mgmt *) frame;
if (len < 24) {
wpa_printf(MSG_DEBUG, "nl80211: Too short action frame");
return;
}
fc = le_to_host16(mgmt->frame_control);
stype = WLAN_FC_GET_STYPE(fc);
os_memset(&event, 0, sizeof(event));
event.rx_action.da = mgmt->da;
event.rx_action.sa = mgmt->sa;
event.rx_action.bssid = mgmt->bssid;
event.rx_action.category = mgmt->u.action.category;
event.rx_action.data = &mgmt->u.action.category + 1;
event.rx_action.len = frame + len - event.rx_action.data;
if (freq)
event.rx_action.freq = nla_get_u32(freq);
wpa_supplicant_event(drv->ctx, EVENT_RX_ACTION, &event);
}
static void mlme_event_action_tx_status(struct wpa_driver_nl80211_data *drv,
struct nlattr *cookie, const u8 *frame,
size_t len, struct nlattr *ack)
{
union wpa_event_data event;
const struct ieee80211_hdr *hdr;
u16 fc;
u64 cookie_val;
if (!cookie)
return;
cookie_val = nla_get_u64(cookie);
wpa_printf(MSG_DEBUG, "nl80211: Action TX status: cookie=0%llx%s",
(long long unsigned int) cookie_val,
cookie_val == drv->send_action_cookie ?
" (match)" : " (unknown)");
if (cookie_val != drv->send_action_cookie)
return;
hdr = (const struct ieee80211_hdr *) frame;
fc = le_to_host16(hdr->frame_control);
os_memset(&event, 0, sizeof(event));
event.tx_status.type = WLAN_FC_GET_TYPE(fc);
event.tx_status.stype = WLAN_FC_GET_STYPE(fc);
event.tx_status.dst = hdr->addr1;
event.tx_status.data = frame;
event.tx_status.data_len = len;
event.tx_status.ack = ack != NULL;
wpa_supplicant_event(drv->ctx, EVENT_TX_STATUS, &event);
}
static void mlme_event(struct wpa_driver_nl80211_data *drv,
enum nl80211_commands cmd, struct nlattr *frame,
struct nlattr *addr, struct nlattr *timed_out)
struct nlattr *addr, struct nlattr *timed_out,
struct nlattr *freq, struct nlattr *ack,
struct nlattr *cookie)
{
if (timed_out && addr) {
mlme_timeout_event(drv, cmd, addr);
@ -615,6 +692,13 @@ static void mlme_event(struct wpa_driver_nl80211_data *drv,
drv->associated = 0;
wpa_supplicant_event(drv->ctx, EVENT_DISASSOC, NULL);
break;
case NL80211_CMD_ACTION:
mlme_event_action(drv, freq, nla_data(frame), nla_len(frame));
break;
case NL80211_CMD_ACTION_TX_STATUS:
mlme_event_action_tx_status(drv, cookie, nla_data(frame),
nla_len(frame), ack);
break;
default:
break;
}
@ -813,8 +897,12 @@ static int process_event(struct nl_msg *msg, void *arg)
case NL80211_CMD_ASSOCIATE:
case NL80211_CMD_DEAUTHENTICATE:
case NL80211_CMD_DISASSOCIATE:
case NL80211_CMD_ACTION:
case NL80211_CMD_ACTION_TX_STATUS:
mlme_event(drv, gnlh->cmd, tb[NL80211_ATTR_FRAME],
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT]);
tb[NL80211_ATTR_MAC], tb[NL80211_ATTR_TIMED_OUT],
tb[NL80211_ATTR_WIPHY_FREQ], tb[NL80211_ATTR_ACK],
tb[NL80211_ATTR_COOKIE]);
break;
case NL80211_CMD_CONNECT:
case NL80211_CMD_ROAM:
@ -1203,6 +1291,48 @@ failed:
}
static int nl80211_register_action_frame(struct wpa_driver_nl80211_data *drv,
const u8 *match, size_t match_len)
{
struct nl_msg *msg;
int ret = -1;
msg = nlmsg_alloc();
if (!msg)
return -1;
genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, 0,
NL80211_CMD_REGISTER_ACTION, 0);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
NLA_PUT(msg, NL80211_ATTR_FRAME_MATCH, match_len, match);
ret = send_and_recv(drv, drv->nl_handle_event, msg, NULL, NULL);
msg = NULL;
if (ret) {
wpa_printf(MSG_DEBUG, "nl80211: Register Action command "
"failed: ret=%d (%s)", ret, strerror(-ret));
wpa_hexdump(MSG_DEBUG, "nl80211: Register Action match",
match, match_len);
goto nla_put_failure;
}
ret = 0;
nla_put_failure:
nlmsg_free(msg);
return ret;
}
static int nl80211_register_action_frames(struct wpa_driver_nl80211_data *drv)
{
if (0) {
/* Public Action frames */
return nl80211_register_action_frame(drv, (u8 *) "\x04", 1);
}
return 0;
}
static int
wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
{
@ -1227,6 +1357,9 @@ wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv)
1, IF_OPER_DORMANT);
#endif /* HOSTAPD */
if (nl80211_register_action_frames(drv) < 0)
return -1;
return 0;
}
@ -4578,6 +4711,70 @@ static int cookie_handler(struct nl_msg *msg, void *arg)
}
static int wpa_driver_nl80211_send_action(void *priv, unsigned int freq,
const u8 *dst, const u8 *src,
const u8 *bssid,
const u8 *data, size_t data_len)
{
struct wpa_driver_nl80211_data *drv = priv;
int ret = -1;
struct nl_msg *msg;
u8 *buf;
struct ieee80211_hdr *hdr;
u64 cookie;
wpa_printf(MSG_DEBUG, "nl80211: Send Action frame (ifindex=%d)",
drv->ifindex);
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, bssid, ETH_ALEN);
if (drv->nlmode == NL80211_IFTYPE_AP) {
ret = wpa_driver_nl80211_send_mlme(priv, buf, 24 + data_len);
os_free(buf);
return ret;
}
msg = nlmsg_alloc();
if (!msg)
return -1;
genlmsg_put(msg, 0, 0, genl_family_get_id(drv->nl80211), 0, 0,
NL80211_CMD_ACTION, 0);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex);
NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq);
NLA_PUT(msg, NL80211_ATTR_FRAME, 24 + data_len, buf);
os_free(buf);
cookie = 0;
ret = send_and_recv_msgs(drv, msg, cookie_handler, &cookie);
msg = NULL;
if (ret) {
wpa_printf(MSG_DEBUG, "nl80211: Action command failed: ret=%d "
"(%s)", ret, strerror(-ret));
goto nla_put_failure;
}
wpa_printf(MSG_DEBUG, "nl80211: Action TX command accepted; "
"cookie 0x%llx", (long long unsigned int) cookie);
drv->send_action_cookie = cookie;
drv->pending_send_action = 1;
ret = 0;
nla_put_failure:
nlmsg_free(msg);
return ret;
}
static int wpa_driver_nl80211_remain_on_channel(void *priv, unsigned int freq,
unsigned int duration)
{
@ -4847,6 +5044,7 @@ 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 */
.send_action = wpa_driver_nl80211_send_action,
.remain_on_channel = wpa_driver_nl80211_remain_on_channel,
.cancel_remain_on_channel =
wpa_driver_nl80211_cancel_remain_on_channel,