mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-22 21:22:37 +01:00
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:
parent
bed116e319
commit
3a8b9a5d0c
264
src/netdev.c
264
src/netdev.c
@ -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);
|
||||
|
16
src/netdev.h
16
src/netdev.h
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user