/* * * 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 #endif #include #include #include "src/module.h" #include "src/anqp.h" #include "src/util.h" #include "src/ie.h" #include "src/scan.h" #include "src/iwd.h" #include "src/mpdu.h" #include "src/frame-xchg.h" #include "linux/nl80211.h" #define ANQP_GROUP 0 struct anqp_request { uint64_t wdev_id; anqp_response_func_t anqp_cb; anqp_destroy_func_t anqp_destroy; void *anqp_data; uint8_t anqp_token; uint32_t frequency; uint8_t *frame; size_t frame_len; uint32_t id; }; static void anqp_destroy(void *user_data) { struct anqp_request *request = user_data; if (request->anqp_destroy) request->anqp_destroy(request->anqp_data); l_free(request->frame); l_free(request); } /* * 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 = 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; if (body_len < 9) return false; /* Skip past category/action since this frame was prefixed matched */ ptr += 2; body_len -= 2; /* dialog token */ token = *ptr++; if (request->anqp_token != token) return false; 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 false; } 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 false; } if (*ptr != IE_TYPE_ADVERTISEMENT_PROTOCOL) { l_error("GAS request not advertisement protocol"); return false; } ptr++; body_len--; adv_proto_len = *ptr++; body_len--; if (body_len < adv_proto_len) return false; ptr += adv_proto_len; body_len -= adv_proto_len; if (body_len < 2) return false; qrlen = l_get_le16(ptr); ptr += 2; if (body_len < qrlen) 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); anqp_destroy(request); return true; } 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; if (error < 0) { result = ANQP_FAILED; l_error("Sending ANQP request failed: %s (%i)", strerror(-error), -error); } if (request->anqp_cb) request->anqp_cb(result, NULL, 0, request->anqp_data); if (request->anqp_destroy) request->anqp_destroy(request->anqp_data); anqp_destroy(request); } static uint8_t *anqp_build_frame(const uint8_t *addr, struct scan_bss *bss, uint8_t anqp_token, const uint8_t *anqp, size_t len, size_t *len_out) { uint8_t *frame = l_malloc(len + 33); uint8_t *ptr; memset(frame, 0, len + 33); l_put_le16(0x00d0, frame + 0); memcpy(frame + 4, bss->addr, 6); memcpy(frame + 10, addr, 6); memcpy(frame + 16, bss->addr, 6); ptr = frame + 24; *ptr++ = 0x04; /* Category: Public */ *ptr++ = 0x0a; /* Action: GAS initial Request */ *ptr++ = anqp_token; /* Dialog Token */ *ptr++ = IE_TYPE_ADVERTISEMENT_PROTOCOL; *ptr++ = 2; *ptr++ = 0x7f; *ptr++ = IE_ADVERTISEMENT_ANQP; l_put_le16(len, ptr); ptr += 2; memcpy(ptr, anqp, len); ptr += len; *len_out = ptr - frame; return frame; } 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; struct iovec iov[2]; request = l_new(struct anqp_request, 1); request->wdev_id = wdev_id; request->frequency = bss->frequency; request->anqp_cb = cb; request->anqp_destroy = destroy; /* * WPA3 Specificiation version 3, Section 9.4: * "A STA shall use a randomized dialog token for every new GAS * exchange." */ l_getrandom(&request->anqp_token, sizeof(request->anqp_token)); request->anqp_data = user_data; request->frame = anqp_build_frame(addr, bss, request->anqp_token, anqp, len, &request->frame_len); iov[0].iov_base = request->frame; iov[0].iov_len = request->frame_len; iov[1].iov_base = NULL; l_debug("Sending ANQP request"); 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); return true; } void anqp_cancel(uint32_t id) { frame_xchg_cancel(id); }