mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-23 06:02: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
260
src/netdev.c
260
src/netdev.c
@ -61,6 +61,7 @@
|
|||||||
#include "src/fils.h"
|
#include "src/fils.h"
|
||||||
#include "src/auth-proto.h"
|
#include "src/auth-proto.h"
|
||||||
#include "src/rtnlutil.h"
|
#include "src/rtnlutil.h"
|
||||||
|
#include "src/anqp.h"
|
||||||
|
|
||||||
#ifndef ENOTSUPP
|
#ifndef ENOTSUPP
|
||||||
#define ENOTSUPP 524
|
#define ENOTSUPP 524
|
||||||
@ -113,6 +114,7 @@ struct netdev {
|
|||||||
struct l_timeout *neighbor_report_timeout;
|
struct l_timeout *neighbor_report_timeout;
|
||||||
struct l_timeout *sa_query_timeout;
|
struct l_timeout *sa_query_timeout;
|
||||||
struct l_timeout *group_handshake_timeout;
|
struct l_timeout *group_handshake_timeout;
|
||||||
|
struct l_timeout *gas_timeout;
|
||||||
uint16_t sa_query_id;
|
uint16_t sa_query_id;
|
||||||
uint8_t prev_bssid[ETH_ALEN];
|
uint8_t prev_bssid[ETH_ALEN];
|
||||||
uint8_t prev_snonce[32];
|
uint8_t prev_snonce[32];
|
||||||
@ -134,6 +136,12 @@ struct netdev {
|
|||||||
|
|
||||||
struct l_io *pae_io; /* for drivers without EAPoL over NL80211 */
|
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 connected : 1;
|
||||||
bool operational : 1;
|
bool operational : 1;
|
||||||
bool rekey_offload_support : 1;
|
bool rekey_offload_support : 1;
|
||||||
@ -601,6 +609,11 @@ static void netdev_free(void *data)
|
|||||||
l_timeout_remove(netdev->neighbor_report_timeout);
|
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)
|
if (netdev->connected)
|
||||||
netdev_connect_free(netdev);
|
netdev_connect_free(netdev);
|
||||||
else if (netdev->disconnect_cmd_id) {
|
else if (netdev->disconnect_cmd_id) {
|
||||||
@ -2642,17 +2655,16 @@ int netdev_leave_adhoc(struct netdev *netdev, netdev_command_cb_t cb,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t netdev_send_action_framev(struct netdev *netdev,
|
static struct l_genl_msg *netdev_build_cmd_frame(struct netdev *netdev,
|
||||||
const uint8_t *to,
|
const uint8_t *to,
|
||||||
struct iovec *iov, size_t iov_len,
|
|
||||||
uint32_t freq,
|
uint32_t freq,
|
||||||
l_genl_msg_func_t callback)
|
struct iovec *iov,
|
||||||
|
size_t iov_len)
|
||||||
{
|
{
|
||||||
struct l_genl_msg *msg;
|
struct l_genl_msg *msg;
|
||||||
struct iovec iovs[iov_len + 1];
|
struct iovec iovs[iov_len + 1];
|
||||||
const uint16_t frame_type = 0x00d0;
|
const uint16_t frame_type = 0x00d0;
|
||||||
uint8_t action_frame[24];
|
uint8_t action_frame[24];
|
||||||
uint32_t id;
|
|
||||||
|
|
||||||
memset(action_frame, 0, 24);
|
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_attr(msg, NL80211_ATTR_WIPHY_FREQ, 4, &freq);
|
||||||
l_genl_msg_append_attrv(msg, NL80211_ATTR_FRAME, iovs, iov_len + 1);
|
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);
|
id = l_genl_family_send(nl80211, msg, callback, netdev, NULL);
|
||||||
|
|
||||||
if (!id)
|
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);
|
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:
|
* 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
|
* 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_resp_prefix[2] = { 0x08, 0x01 };
|
||||||
const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
|
const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
|
||||||
const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
|
const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
|
||||||
|
const uint8_t action_gas_response_prefix[] = { 0x04, 0x0b };
|
||||||
struct l_io *pae_io = NULL;
|
struct l_io *pae_io = NULL;
|
||||||
const struct l_settings *settings = iwd_get_config();
|
const struct l_settings *settings = iwd_get_config();
|
||||||
bool pae_over_nl80211;
|
bool pae_over_nl80211;
|
||||||
@ -4514,6 +4761,11 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg)
|
|||||||
sizeof(action_ft_response_prefix),
|
sizeof(action_ft_response_prefix),
|
||||||
netdev_ft_response_frame_event, NULL);
|
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 */
|
/* Set RSSI threshold for CQM notifications */
|
||||||
if (netdev->type == NL80211_IFTYPE_STATION)
|
if (netdev->type == NL80211_IFTYPE_STATION)
|
||||||
netdev_cqm_rssi_update(netdev);
|
netdev_cqm_rssi_update(netdev);
|
||||||
|
16
src/netdev.h
16
src/netdev.h
@ -66,6 +66,12 @@ enum netdev_iftype {
|
|||||||
NETDEV_IFTYPE_P2P_GO,
|
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,
|
typedef void (*netdev_command_cb_t)(struct netdev *netdev, int result,
|
||||||
void *user_data);
|
void *user_data);
|
||||||
/*
|
/*
|
||||||
@ -124,6 +130,16 @@ typedef void (*netdev_station_watch_func_t)(struct netdev *netdev,
|
|||||||
const uint8_t *mac, bool added,
|
const uint8_t *mac, bool added,
|
||||||
void *user_data);
|
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);
|
struct wiphy *netdev_get_wiphy(struct netdev *netdev);
|
||||||
const uint8_t *netdev_get_address(struct netdev *netdev);
|
const uint8_t *netdev_get_address(struct netdev *netdev);
|
||||||
uint32_t netdev_get_ifindex(struct netdev *netdev);
|
uint32_t netdev_get_ifindex(struct netdev *netdev);
|
||||||
|
Loading…
Reference in New Issue
Block a user