netdev: support basic ANQP requests

This adds a new API netdev_anqp_request which will send out a GAS
request, parses the GAS portion of the response and forwards the
ANQP response to the callers callback.
This commit is contained in:
James Prestwood 2019-06-14 14:56:13 -07:00 committed by Denis Kenzior
parent bed116e319
commit 3a8b9a5d0c
2 changed files with 274 additions and 6 deletions

View File

@ -61,6 +61,7 @@
#include "src/fils.h"
#include "src/auth-proto.h"
#include "src/rtnlutil.h"
#include "src/anqp.h"
#ifndef ENOTSUPP
#define ENOTSUPP 524
@ -113,6 +114,7 @@ struct netdev {
struct l_timeout *neighbor_report_timeout;
struct l_timeout *sa_query_timeout;
struct l_timeout *group_handshake_timeout;
struct l_timeout *gas_timeout;
uint16_t sa_query_id;
uint8_t prev_bssid[ETH_ALEN];
uint8_t prev_snonce[32];
@ -134,6 +136,12 @@ struct netdev {
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
netdev_anqp_response_func_t anqp_cb;
netdev_destroy_func_t anqp_destroy;
void *anqp_data;
uint64_t anqp_cookie;
uint8_t anqp_token;
bool connected : 1;
bool operational : 1;
bool rekey_offload_support : 1;
@ -601,6 +609,11 @@ static void netdev_free(void *data)
l_timeout_remove(netdev->neighbor_report_timeout);
}
if (netdev->anqp_destroy)
netdev->anqp_destroy(netdev->anqp_data);
l_timeout_remove(netdev->gas_timeout);
if (netdev->connected)
netdev_connect_free(netdev);
else if (netdev->disconnect_cmd_id) {
@ -2642,17 +2655,16 @@ int netdev_leave_adhoc(struct netdev *netdev, netdev_command_cb_t cb,
return 0;
}
static uint32_t netdev_send_action_framev(struct netdev *netdev,
const uint8_t *to,
struct iovec *iov, size_t iov_len,
uint32_t freq,
l_genl_msg_func_t callback)
static struct l_genl_msg *netdev_build_cmd_frame(struct netdev *netdev,
const uint8_t *to,
uint32_t freq,
struct iovec *iov,
size_t iov_len)
{
struct l_genl_msg *msg;
struct iovec iovs[iov_len + 1];
const uint16_t frame_type = 0x00d0;
uint8_t action_frame[24];
uint32_t id;
memset(action_frame, 0, 24);
@ -2671,6 +2683,19 @@ static uint32_t netdev_send_action_framev(struct netdev *netdev,
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq);
l_genl_msg_append_attrv(msg, NL80211_ATTR_FRAME, iovs, iov_len + 1);
return msg;
}
static uint32_t netdev_send_action_framev(struct netdev *netdev,
const uint8_t *to,
struct iovec *iov, size_t iov_len,
uint32_t freq,
l_genl_msg_func_t callback)
{
uint32_t id;
struct l_genl_msg *msg = netdev_build_cmd_frame(netdev, to, freq,
iov, iov_len);
id = l_genl_family_send(nl80211, msg, callback, netdev, NULL);
if (!id)
@ -2693,6 +2718,227 @@ static uint32_t netdev_send_action_frame(struct netdev *netdev,
return netdev_send_action_framev(netdev, to, iov, 1, freq, callback);
}
static void netdev_gas_request_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
if (l_genl_msg_get_error(msg) != 0)
goto error;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_COOKIE:
if (len != 8)
goto error;
netdev->anqp_cookie = l_get_u64(data);
break;
}
}
return;
error:
l_debug("Error sending CMD_FRAME (%d)", l_genl_msg_get_error(msg));
if (netdev->anqp_cb)
netdev->anqp_cb(netdev, NETDEV_ANQP_FAILED, NULL, 0,
netdev->anqp_data);
netdev->anqp_cb = NULL;
if (netdev->anqp_destroy)
netdev->anqp_destroy(netdev->anqp_data);
netdev->anqp_destroy = NULL;
l_timeout_remove(netdev->gas_timeout);
netdev->gas_timeout = NULL;
}
static void netdev_gas_response_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *user_data)
{
const uint8_t *ptr = body;
uint16_t status_code;
uint16_t delay;
uint16_t qrlen;
uint8_t adv_proto_len;
if (body_len < 9)
return;
/* Skip past category/action since this frame was prefixed matched */
ptr += 2;
body_len -= 2;
/* dialog token */
if (netdev->anqp_token != *ptr++)
return;
status_code = l_get_le16(ptr);
ptr += 2;
body_len -= 2;
if (status_code != 0) {
l_error("Bad status code on GAS response %u", status_code);
return;
}
delay = l_get_le16(ptr);
ptr += 2;
body_len -= 2;
/*
* IEEE 80211-2016 Section 9.6.8.13
*
* The value 0 will be returned by the STA when a Query Response is
* provided in this frame
*/
if (delay != 0) {
l_error("GAS comeback delay was not zero");
return;
}
if (*ptr != IE_TYPE_ADVERTISEMENT_PROTOCOL) {
l_error("GAS request not advertisement protocol");
return;
}
ptr++;
body_len--;
adv_proto_len = *ptr++;
body_len--;
if (body_len < adv_proto_len)
return;
ptr += adv_proto_len;
body_len -= adv_proto_len;
if (body_len < 2)
return;
qrlen = l_get_le16(ptr);
ptr += 2;
if (body_len < qrlen)
return;
l_timeout_remove(netdev->gas_timeout);
netdev->gas_timeout = NULL;
netdev->anqp_token++;
if (netdev->anqp_cb)
netdev->anqp_cb(netdev, NETDEV_ANQP_SUCCESS, ptr, qrlen,
netdev->anqp_data);
netdev->anqp_cb = NULL;
if (netdev->anqp_destroy)
netdev->anqp_destroy(netdev->anqp_data);
netdev->anqp_destroy = NULL;
return;
}
static void netdev_gas_timeout_cb(struct l_timeout *timeout, void *user_data)
{
struct netdev *netdev = user_data;
netdev_destroy_func_t destroy = netdev->anqp_destroy;
void *anqp_data = netdev->anqp_data;
l_debug("GAS request timed out");
l_timeout_remove(netdev->gas_timeout);
netdev->gas_timeout = NULL;
netdev->anqp_token++;
if (netdev->anqp_cb)
netdev->anqp_cb(netdev, NETDEV_ANQP_TIMEOUT, NULL, 0,
netdev->anqp_data);
/* allows anqp_request to be re-entrant */
if (destroy)
destroy(anqp_data);
}
uint32_t netdev_anqp_request(struct netdev *netdev, struct scan_bss *bss,
const uint8_t *anqp, size_t len,
netdev_anqp_response_func_t cb,
void *user_data,
netdev_destroy_func_t destroy)
{
struct wiphy *wiphy = netdev->wiphy;
uint8_t frame[512];
struct l_genl_msg *msg;
struct iovec iov[2];
uint32_t id;
uint32_t duration = 300;
/*
* If this is ever extended and used while associated some logic will
* need to be added here to determine if we need to go off channel.
*/
if (!wiphy_can_offchannel_tx(wiphy)) {
l_error("ANQP failed, driver does not support offchannel TX");
return 0;
}
frame[0] = 0x04; /* Category: Public */
frame[1] = 0x0a; /* Action: GAS initial Request */
frame[2] = netdev->anqp_token; /* Dialog Token */
frame[3] = IE_TYPE_ADVERTISEMENT_PROTOCOL;
frame[4] = 2;
frame[5] = 0x7f;
frame[6] = IE_ADVERTISEMENT_ANQP;
l_put_le16(len, frame + 7);
iov[0].iov_base = frame;
iov[0].iov_len = 9;
iov[1].iov_base = (void *)anqp;
iov[1].iov_len = len;
msg = netdev_build_cmd_frame(netdev, bss->addr, bss->frequency,
iov, 2);
l_genl_msg_append_attr(msg, NL80211_ATTR_OFFCHANNEL_TX_OK, 0, "");
l_genl_msg_append_attr(msg, NL80211_ATTR_DURATION, 4, &duration);
id = l_genl_family_send(nl80211, msg, netdev_gas_request_cb,
netdev, NULL);
if (!id) {
l_genl_msg_unref(msg);
return 0;
}
netdev->anqp_cb = cb;
netdev->anqp_data = user_data;
netdev->anqp_cookie = 0;
netdev->anqp_destroy = destroy;
/*
* The kernel seems to take quite a while to send out public action
* frames (maybe switching frequencies or coming out of idle?). Because
* of this we need a rather large timeout.
*/
netdev->gas_timeout = l_timeout_create(6, netdev_gas_timeout_cb,
netdev, NULL);
return id;
}
/*
* Build an FT Authentication Request frame according to 12.5.2 / 12.5.4:
* RSN or non-RSN Over-the-air FT Protocol, with the IE contents
@ -4368,6 +4614,7 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg)
const uint8_t action_sa_query_resp_prefix[2] = { 0x08, 0x01 };
const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
const uint8_t action_gas_response_prefix[] = { 0x04, 0x0b };
struct l_io *pae_io = NULL;
const struct l_settings *settings = iwd_get_config();
bool pae_over_nl80211;
@ -4514,6 +4761,11 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg)
sizeof(action_ft_response_prefix),
netdev_ft_response_frame_event, NULL);
netdev_frame_watch_add(netdev, 0x00d0, action_gas_response_prefix,
sizeof(action_gas_response_prefix),
netdev_gas_response_frame_event, NULL);
/* Set RSSI threshold for CQM notifications */
if (netdev->type == NL80211_IFTYPE_STATION)
netdev_cqm_rssi_update(netdev);

View File

@ -66,6 +66,12 @@ enum netdev_iftype {
NETDEV_IFTYPE_P2P_GO,
};
enum netdev_anqp_result {
NETDEV_ANQP_SUCCESS,
NETDEV_ANQP_TIMEOUT,
NETDEV_ANQP_FAILED,
};
typedef void (*netdev_command_cb_t)(struct netdev *netdev, int result,
void *user_data);
/*
@ -124,6 +130,16 @@ typedef void (*netdev_station_watch_func_t)(struct netdev *netdev,
const uint8_t *mac, bool added,
void *user_data);
typedef void (*netdev_anqp_response_func_t)(struct netdev *netdev,
enum netdev_anqp_result result,
const void *anqp, size_t len,
void *user_data);
uint32_t netdev_anqp_request(struct netdev *netdev, struct scan_bss *bss,
const uint8_t *anqp, size_t len,
netdev_anqp_response_func_t cb,
void *user_data,
netdev_destroy_func_t destroy);
struct wiphy *netdev_get_wiphy(struct netdev *netdev);
const uint8_t *netdev_get_address(struct netdev *netdev);
uint32_t netdev_get_ifindex(struct netdev *netdev);