The new frame-xchg module now handles a lot of what ANQP used to do. ANQP now does not need to depend on nl80211/netdev for building and sending frames. It also no longer needs any of the request lookups, frame watches or to maintain a queue of requests because frame-xchg filters this for us. From an API perspective: - anqp_request() was changed to take the wdev_id rather than ifindex. - anqp_cancel() was added so that station can properly clean up ANQP requests if the device disappears. During testing a bug was also fixed in station on the timeout path where the request queue would get popped twice. --- src/anqp.c | 443 +++++++++++--------------------------------------- src/anqp.h | 3 +- src/station.c | 19 +-- 3 files changed, 103 insertions(+), 362 deletions(-) diff --git a/src/anqp.c b/src/anqp.c index 6627bd81..3b597835 100644 --- a/src/anqp.c +++ b/src/anqp.c @@ -31,105 +31,61 @@ #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 "src/frame-xchg.h" #include "linux/nl80211.h" +#define ANQP_GROUP 0 + struct anqp_request { - uint32_t ifindex; + uint64_t wdev_id; anqp_response_func_t anqp_cb; anqp_destroy_func_t anqp_destroy; void *anqp_data; - uint64_t anqp_cookie; uint8_t anqp_token; + uint32_t frequency; + uint8_t *frame; + size_t frame_len; + uint32_t id; }; -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->frame); 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) +/* + * By using frame-xchg we should get called back here for any frame matching our + * prefix until the duration expires. If frame-xchg is never signalled 'done' + * (returning true) we should get a timeout in anqp_frame_timeout. This is + * why we drop any improperly formatted frames without cleaning up the request. + */ +static bool anqp_response_frame_event(const struct mmpdu_header *hdr, + const void *body, size_t body_len, + int rssi, void *user_data) { - struct anqp_request *request; + struct anqp_request *request = user_data; 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; + return false; /* Skip past category/action since this frame was prefixed matched */ ptr += 2; @@ -138,12 +94,8 @@ static void anqp_response_frame_event(uint32_t ifindex, /* dialog token */ token = *ptr++; - match.ifindex = ifindex; - match.token = token; - - request = l_queue_find(anqp_requests, match_token, &match); - if (!request) - return; + if (request->anqp_token != token) + return false; status_code = l_get_le16(ptr); ptr += 2; @@ -151,7 +103,7 @@ static void anqp_response_frame_event(uint32_t ifindex, if (status_code != 0) { l_error("Bad status code on GAS response %u", status_code); - return; + return false; } delay = l_get_le16(ptr); @@ -166,12 +118,12 @@ static void anqp_response_frame_event(uint32_t ifindex, */ if (delay != 0) { l_error("GAS comeback delay was not zero"); - return; + return false; } if (*ptr != IE_TYPE_ADVERTISEMENT_PROTOCOL) { l_error("GAS request not advertisement protocol"); - return; + return false; } ptr++; @@ -181,339 +133,128 @@ static void anqp_response_frame_event(uint32_t ifindex, body_len--; if (body_len < adv_proto_len) - return; + return false; ptr += adv_proto_len; body_len -= adv_proto_len; if (body_len < 2) - return; + return false; qrlen = l_get_le16(ptr); ptr += 2; if (body_len < qrlen) - return; - - l_queue_remove(anqp_requests, request); + return false; 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); + request->anqp_cb(ANQP_SUCCESS, ptr, qrlen, request->anqp_data); - l_free(request); + anqp_destroy(request); - return; + return true; } -static void netdev_gas_timeout_cb(void *user_data) +static const struct frame_xchg_prefix anqp_frame_prefix = { + .data = (uint8_t []) { + 0x04, 0x0b, + }, + .len = 2, +}; + +static void anqp_frame_timeout(int error, void *user_data) { struct anqp_request *request = user_data; + enum anqp_result result = ANQP_TIMEOUT; - l_debug("GAS request timed out"); + if (error < 0) { + result = ANQP_FAILED; + l_error("Sending ANQP request failed: %s (%i)", + strerror(-error), -error); + } if (request->anqp_cb) - request->anqp_cb(ANQP_TIMEOUT, NULL, 0, - request->anqp_data); + request->anqp_cb(result, 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); + anqp_destroy(request); } -static bool match_cookie(const void *a, const void *b) +static uint8_t *anqp_build_frame(const uint8_t *addr, struct scan_bss *bss, + const uint8_t *anqp, size_t len, + size_t *len_out) { - const struct anqp_request *request = a; - const struct cookie_match { - uint64_t cookie; - uint32_t ifindex; - } *match = b; + uint8_t *frame = l_malloc(len + 33); + uint8_t *ptr; - if (match->ifindex != request->ifindex) - return false; + memset(frame, 0, len + 33); - if (match->cookie != request->anqp_cookie) - return false; + l_put_le16(0x00d0, frame + 0); + memcpy(frame + 4, bss->addr, 6); + memcpy(frame + 10, addr, 6); + memcpy(frame + 16, bss->addr, 6); - return true; -} + ptr = frame + 24; -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; + *ptr++ = 0x04; /* Category: Public */ + *ptr++ = 0x0a; /* Action: GAS initial Request */ + *ptr++ = anqp_token++; /* Dialog Token */ + *ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL; + *ptr++ = 2; - l_debug(""); - - if (nl80211_parse_attrs(msg, NL80211_ATTR_COOKIE, &cookie, - NL80211_ATTR_UNSPEC) < 0) - return; - - match.cookie = cookie; - match.ifindex = ifindex; + *ptr++ = 0x7f; + *ptr++ = IE_ADVERTISEMENT_ANQP; + l_put_le16(len, ptr); + ptr += 2; - request = l_queue_find(anqp_requests, match_cookie, &match); - if (!request) - return; + memcpy(ptr, anqp, len); + ptr += len; - if (cookie != request->anqp_cookie) - return; + *len_out = ptr - frame; - netdev_gas_timeout_cb(request); + return frame; } -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) +uint32_t anqp_request(uint64_t wdev_id, 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->wdev_id = wdev_id; + request->frequency = bss->frequency; request->anqp_cb = cb; request->anqp_destroy = destroy; - request->anqp_token = anqp_token++; + 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; - } - } + request->frame = anqp_build_frame(addr, bss, anqp, len, + &request->frame_len); - 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(); + iov[0].iov_base = request->frame; + iov[0].iov_len = request->frame_len; + iov[1].iov_base = NULL; - netdev_watch = netdev_watch_add(anqp_netdev_watch, NULL, NULL); + l_debug("Sending ANQP request"); - unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME, - anqp_unicast_notify, - NULL, NULL); + request->id = frame_xchg_start(request->wdev_id, iov, + request->frequency, 0, 300, 0, + ANQP_GROUP, anqp_frame_timeout, request, NULL, + &anqp_frame_prefix, anqp_response_frame_event, + NULL); - if (!l_genl_family_register(nl80211, "mlme", anqp_mlme_notify, - NULL, NULL)) - l_error("Registering for MLME notification failed"); - - return 0; + return true; } -static void anqp_exit(void) +void anqp_cancel(uint32_t id) { - 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); + frame_xchg_cancel(id); } - -IWD_MODULE(anqp, anqp_init, anqp_exit); -IWD_MODULE_DEPENDS(anqp, netdev); diff --git a/src/anqp.h b/src/anqp.h index 998277dd..4d96c9ec 100644 --- a/src/anqp.h +++ b/src/anqp.h @@ -34,7 +34,8 @@ typedef void (*anqp_response_func_t)(enum anqp_result result, const void *anqp, size_t len, void *user_data); -uint32_t anqp_request(uint32_t ifindex, const uint8_t *addr, +uint32_t anqp_request(uint64_t wdev_id, 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); +void anqp_cancel(uint32_t id); diff --git a/src/station.c b/src/station.c index 3719e0b9..a19195f4 100644 --- a/src/station.c +++ b/src/station.c @@ -447,6 +447,9 @@ static void remove_anqp(void *data) { struct anqp_entry *entry = data; + if (entry->pending) + anqp_cancel(entry->pending); + l_free(entry); } @@ -476,12 +479,9 @@ static void station_anqp_response_cb(enum anqp_result result, char **realms = NULL; struct nai_search search; - entry->pending = 0; - l_debug(""); - if (result == ANQP_TIMEOUT) { - l_queue_remove(station->anqp_pending, entry); + if (result != ANQP_SUCCESS) { /* TODO: try next BSS */ goto request_done; } @@ -583,11 +583,10 @@ static bool station_start_anqp(struct station *station, struct network *network, * these are checked in hs20_find_settings_file. */ - entry->pending = anqp_request(netdev_get_ifindex(station->netdev), - netdev_get_address(station->netdev), - bss, anqp, ptr - anqp, - station_anqp_response_cb, - entry, l_free); + entry->pending = anqp_request(netdev_get_wdev_id(station->netdev), + netdev_get_address(station->netdev), bss, anqp, + ptr - anqp, station_anqp_response_cb, + entry, NULL); if (!entry->pending) { l_free(entry); return false; @@ -3252,7 +3251,7 @@ static void station_free(struct station *station) watchlist_destroy(&station->state_watches); - l_queue_destroy(station->anqp_pending, l_free); + l_queue_destroy(station->anqp_pending, remove_anqp); l_free(station); } -- 2.21.1