mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2026-02-15 11:07:55 +01:00
Check the iftype before registering ANQP on new interface. Not that the check here and in rrm.c (which already checks the iftype) may need to be extended to run on NETDEV_WATCH_EVENT_UP because a device could be created with a different iftype and then have the iftype changed before powering up.
520 lines
11 KiB
C
520 lines
11 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2019 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "src/module.h"
|
|
#include "src/anqp.h"
|
|
#include "src/util.h"
|
|
#include "src/eap-private.h"
|
|
#include "src/ie.h"
|
|
#include "src/nl80211util.h"
|
|
#include "src/nl80211cmd.h"
|
|
#include "src/scan.h"
|
|
#include "src/netdev.h"
|
|
#include "src/iwd.h"
|
|
#include "src/mpdu.h"
|
|
#include "src/wiphy.h"
|
|
|
|
#include "linux/nl80211.h"
|
|
|
|
struct anqp_request {
|
|
uint32_t ifindex;
|
|
anqp_response_func_t anqp_cb;
|
|
anqp_destroy_func_t anqp_destroy;
|
|
void *anqp_data;
|
|
uint64_t anqp_cookie;
|
|
uint8_t anqp_token;
|
|
};
|
|
|
|
static struct l_genl_family *nl80211 = NULL;
|
|
|
|
static struct l_queue *anqp_requests;
|
|
static uint8_t anqp_token = 0;
|
|
|
|
static uint32_t netdev_watch;
|
|
static uint32_t unicast_watch;
|
|
|
|
static void anqp_destroy(void *user_data)
|
|
{
|
|
struct anqp_request *request = user_data;
|
|
|
|
if (request->anqp_destroy)
|
|
request->anqp_destroy(request->anqp_data);
|
|
}
|
|
|
|
static void netdev_gas_request_cb(struct l_genl_msg *msg, void *user_data)
|
|
{
|
|
struct anqp_request *request = user_data;
|
|
|
|
if (l_genl_msg_get_error(msg) != 0)
|
|
goto error;
|
|
|
|
if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &request->anqp_cookie,
|
|
NL80211_ATTR_UNSPEC) < 0)
|
|
goto error;
|
|
|
|
return;
|
|
|
|
error:
|
|
l_debug("Error sending CMD_FRAME (%d)", l_genl_msg_get_error(msg));
|
|
|
|
if (request->anqp_cb)
|
|
request->anqp_cb(ANQP_FAILED, NULL, 0, request->anqp_data);
|
|
|
|
if (request->anqp_destroy)
|
|
request->anqp_destroy(request->anqp_data);
|
|
|
|
l_free(request);
|
|
}
|
|
|
|
static bool match_token(const void *a, const void *b)
|
|
{
|
|
const struct anqp_request *request = a;
|
|
const struct token_match {
|
|
uint32_t ifindex;
|
|
uint8_t token;
|
|
|
|
} *match = b;
|
|
|
|
if (request->ifindex != match->ifindex)
|
|
return false;
|
|
|
|
if (request->anqp_token != match->token)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void anqp_response_frame_event(uint32_t ifindex,
|
|
const struct mmpdu_header *hdr,
|
|
const void *body, size_t body_len)
|
|
{
|
|
struct anqp_request *request;
|
|
const uint8_t *ptr = body;
|
|
uint16_t status_code;
|
|
uint16_t delay;
|
|
uint16_t qrlen;
|
|
uint8_t adv_proto_len;
|
|
uint8_t token;
|
|
struct token_match {
|
|
uint32_t ifindex;
|
|
uint8_t token;
|
|
|
|
} match;
|
|
|
|
if (body_len < 9)
|
|
return;
|
|
|
|
/* Skip past category/action since this frame was prefixed matched */
|
|
ptr += 2;
|
|
body_len -= 2;
|
|
|
|
/* dialog token */
|
|
token = *ptr++;
|
|
|
|
match.ifindex = ifindex;
|
|
match.token = token;
|
|
|
|
request = l_queue_find(anqp_requests, match_token, &match);
|
|
if (!request)
|
|
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_queue_remove(anqp_requests, request);
|
|
|
|
l_debug("ANQP response received from "MAC, MAC_STR(hdr->address_2));
|
|
|
|
if (request->anqp_cb)
|
|
request->anqp_cb(ANQP_SUCCESS, ptr, qrlen,
|
|
request->anqp_data);
|
|
|
|
if (request->anqp_destroy)
|
|
request->anqp_destroy(request->anqp_data);
|
|
|
|
l_free(request);
|
|
|
|
return;
|
|
}
|
|
|
|
static void netdev_gas_timeout_cb(void *user_data)
|
|
{
|
|
struct anqp_request *request = user_data;
|
|
|
|
l_debug("GAS request timed out");
|
|
|
|
if (request->anqp_cb)
|
|
request->anqp_cb(ANQP_TIMEOUT, NULL, 0,
|
|
request->anqp_data);
|
|
|
|
/* allows anqp_request to be re-entrant */
|
|
if (request->anqp_destroy)
|
|
request->anqp_destroy(request->anqp_data);
|
|
|
|
l_queue_remove(anqp_requests, request);
|
|
l_free(request);
|
|
}
|
|
|
|
static bool match_cookie(const void *a, const void *b)
|
|
{
|
|
const struct anqp_request *request = a;
|
|
const struct cookie_match {
|
|
uint64_t cookie;
|
|
uint32_t ifindex;
|
|
} *match = b;
|
|
|
|
if (match->ifindex != request->ifindex)
|
|
return false;
|
|
|
|
if (match->cookie != request->anqp_cookie)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void anqp_frame_wait_cancel_event(struct l_genl_msg *msg,
|
|
uint32_t ifindex)
|
|
{
|
|
uint64_t cookie;
|
|
struct anqp_request *request;
|
|
struct cookie_match {
|
|
uint64_t cookie;
|
|
uint32_t ifindex;
|
|
} match;
|
|
|
|
l_debug("");
|
|
|
|
if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &cookie,
|
|
NL80211_ATTR_UNSPEC) < 0)
|
|
return;
|
|
|
|
match.cookie = cookie;
|
|
match.ifindex = ifindex;
|
|
|
|
request = l_queue_find(anqp_requests, match_cookie, &match);
|
|
if (!request)
|
|
return;
|
|
|
|
if (cookie != request->anqp_cookie)
|
|
return;
|
|
|
|
netdev_gas_timeout_cb(request);
|
|
}
|
|
|
|
uint32_t anqp_request(uint32_t ifindex, const uint8_t *addr,
|
|
struct scan_bss *bss, const uint8_t *anqp,
|
|
size_t len, anqp_response_func_t cb,
|
|
void *user_data, anqp_destroy_func_t destroy)
|
|
{
|
|
struct anqp_request *request;
|
|
uint8_t frame[512];
|
|
struct l_genl_msg *msg;
|
|
struct iovec iov[2];
|
|
uint32_t id;
|
|
uint32_t duration = 300;
|
|
struct netdev *netdev = netdev_find(ifindex);
|
|
|
|
if (!netdev)
|
|
return 0;
|
|
|
|
/*
|
|
* TODO: Netdev dependencies will eventually be removed so we need
|
|
* another way to figure out wiphy capabilities.
|
|
*/
|
|
if (!wiphy_can_offchannel_tx(netdev_get_wiphy(netdev))) {
|
|
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] = 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;
|
|
|
|
request = l_new(struct anqp_request, 1);
|
|
|
|
request->ifindex = ifindex;
|
|
request->anqp_cb = cb;
|
|
request->anqp_destroy = destroy;
|
|
request->anqp_token = anqp_token++;
|
|
request->anqp_data = user_data;
|
|
|
|
msg = nl80211_build_cmd_frame(ifindex, addr, 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,
|
|
request, NULL);
|
|
|
|
if (!id) {
|
|
l_debug("Failed to send ANQP request");
|
|
l_genl_msg_unref(msg);
|
|
l_free(request);
|
|
return 0;
|
|
}
|
|
|
|
l_debug("ANQP request sent to "MAC, MAC_STR(bss->addr));
|
|
|
|
l_queue_push_head(anqp_requests, request);
|
|
|
|
return id;
|
|
}
|
|
|
|
static void netdev_frame_cb(struct l_genl_msg *msg, void *user_data)
|
|
{
|
|
if (l_genl_msg_get_error(msg) < 0)
|
|
l_error("Could not register frame watch type %04x: %i",
|
|
L_PTR_TO_UINT(user_data), l_genl_msg_get_error(msg));
|
|
}
|
|
|
|
static void anqp_register_frame(uint32_t ifindex)
|
|
{
|
|
struct l_genl_msg *msg;
|
|
uint16_t frame_type = 0x00d0;
|
|
uint8_t prefix[] = { 0x04, 0x0b };
|
|
|
|
msg = l_genl_msg_new_sized(NL80211_CMD_REGISTER_FRAME, 34);
|
|
|
|
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &ifindex);
|
|
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_TYPE, 2, &frame_type);
|
|
l_genl_msg_append_attr(msg, NL80211_ATTR_FRAME_MATCH,
|
|
sizeof(prefix), prefix);
|
|
|
|
l_genl_family_send(nl80211, msg, netdev_frame_cb,
|
|
L_UINT_TO_PTR(frame_type), NULL);
|
|
}
|
|
|
|
static void anqp_netdev_watch(struct netdev *netdev,
|
|
enum netdev_watch_event event, void *user_data)
|
|
{
|
|
switch (event) {
|
|
case NETDEV_WATCH_EVENT_NEW:
|
|
if (netdev_get_iftype(netdev) == NETDEV_IFTYPE_STATION)
|
|
anqp_register_frame(netdev_get_ifindex(netdev));
|
|
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void anqp_unicast_notify(struct l_genl_msg *msg, void *user_data)
|
|
{
|
|
const struct mmpdu_header *mpdu = NULL;
|
|
const uint8_t *body;
|
|
struct l_genl_attr attr;
|
|
uint16_t type, len;
|
|
uint16_t frame_len = 0;
|
|
const void *data;
|
|
uint8_t cmd;
|
|
uint32_t ifindex = 0;
|
|
|
|
if (l_queue_isempty(anqp_requests))
|
|
return;
|
|
|
|
cmd = l_genl_msg_get_command(msg);
|
|
if (!cmd)
|
|
return;
|
|
|
|
if (!l_genl_attr_init(&attr, msg))
|
|
return;
|
|
|
|
while (l_genl_attr_next(&attr, &type, &len, &data)) {
|
|
switch (type) {
|
|
case NL80211_ATTR_IFINDEX:
|
|
if (len != sizeof(uint32_t)) {
|
|
l_warn("Invalid interface index attribute");
|
|
return;
|
|
}
|
|
|
|
ifindex = *((uint32_t *) data);
|
|
|
|
break;
|
|
case NL80211_ATTR_FRAME:
|
|
if (mpdu)
|
|
return;
|
|
|
|
mpdu = mpdu_validate(data, len);
|
|
if (!mpdu)
|
|
l_error("Frame didn't validate as MMPDU");
|
|
|
|
frame_len = len;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ifindex || !mpdu)
|
|
return;
|
|
|
|
body = mmpdu_body(mpdu);
|
|
|
|
anqp_response_frame_event(ifindex, mpdu, body,
|
|
(const uint8_t *) mpdu + frame_len - body);
|
|
}
|
|
|
|
static void anqp_mlme_notify(struct l_genl_msg *msg, void *user_data)
|
|
{
|
|
struct l_genl_attr attr;
|
|
uint16_t type, len;
|
|
const void *data;
|
|
uint8_t cmd;
|
|
uint32_t ifindex = 0;
|
|
|
|
if (l_queue_isempty(anqp_requests))
|
|
return;
|
|
|
|
cmd = l_genl_msg_get_command(msg);
|
|
|
|
l_debug("MLME notification %s(%u)", nl80211cmd_to_string(cmd), cmd);
|
|
|
|
if (!l_genl_attr_init(&attr, msg))
|
|
return;
|
|
|
|
while (l_genl_attr_next(&attr, &type, &len, &data)) {
|
|
switch (type) {
|
|
case NL80211_ATTR_IFINDEX:
|
|
if (len != sizeof(uint32_t)) {
|
|
l_warn("Invalid interface index attribute");
|
|
return;
|
|
}
|
|
|
|
ifindex = *((uint32_t *) data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ifindex) {
|
|
l_warn("MLME notification is missing ifindex attribute");
|
|
return;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case NL80211_CMD_FRAME_WAIT_CANCEL:
|
|
anqp_frame_wait_cancel_event(msg, ifindex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int anqp_init(void)
|
|
{
|
|
struct l_genl *genl = iwd_get_genl();
|
|
|
|
nl80211 = l_genl_family_new(genl, NL80211_GENL_NAME);
|
|
|
|
anqp_requests = l_queue_new();
|
|
|
|
netdev_watch = netdev_watch_add(anqp_netdev_watch, NULL, NULL);
|
|
|
|
unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME,
|
|
anqp_unicast_notify,
|
|
NULL, NULL);
|
|
|
|
if (!l_genl_family_register(nl80211, "mlme", anqp_mlme_notify,
|
|
NULL, NULL))
|
|
l_error("Registering for MLME notification failed");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void anqp_exit(void)
|
|
{
|
|
struct l_genl *genl = iwd_get_genl();
|
|
|
|
l_genl_family_free(nl80211);
|
|
nl80211 = NULL;
|
|
|
|
l_queue_destroy(anqp_requests, anqp_destroy);
|
|
|
|
netdev_watch_remove(netdev_watch);
|
|
|
|
l_genl_remove_unicast_watch(genl, unicast_watch);
|
|
}
|
|
|
|
IWD_MODULE(anqp, anqp_init, anqp_exit);
|
|
IWD_MODULE_DEPENDS(anqp, netdev);
|