1252190Srpaulo/* 2252190Srpaulo * Generic advertisement service (GAS) query 3252190Srpaulo * Copyright (c) 2009, Atheros Communications 4281806Srpaulo * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. 5281806Srpaulo * Copyright (c) 2011-2014, Jouni Malinen <j@w1.fi> 6252190Srpaulo * 7252190Srpaulo * This software may be distributed under the terms of the BSD license. 8252190Srpaulo * See README for more details. 9252190Srpaulo */ 10252190Srpaulo 11252190Srpaulo#include "includes.h" 12252190Srpaulo 13252190Srpaulo#include "common.h" 14252190Srpaulo#include "utils/eloop.h" 15252190Srpaulo#include "common/ieee802_11_defs.h" 16252190Srpaulo#include "common/gas.h" 17281806Srpaulo#include "common/wpa_ctrl.h" 18281806Srpaulo#include "rsn_supp/wpa.h" 19252190Srpaulo#include "wpa_supplicant_i.h" 20337817Scy#include "config.h" 21252190Srpaulo#include "driver_i.h" 22252190Srpaulo#include "offchannel.h" 23252190Srpaulo#include "gas_query.h" 24252190Srpaulo 25252190Srpaulo 26252190Srpaulo/** GAS query timeout in seconds */ 27281806Srpaulo#define GAS_QUERY_TIMEOUT_PERIOD 2 28252190Srpaulo 29337817Scy/* GAS query wait-time / duration in ms */ 30337817Scy#define GAS_QUERY_WAIT_TIME_INITIAL 1000 31337817Scy#define GAS_QUERY_WAIT_TIME_COMEBACK 150 32252190Srpaulo 33252190Srpaulo/** 34252190Srpaulo * struct gas_query_pending - Pending GAS query 35252190Srpaulo */ 36252190Srpaulostruct gas_query_pending { 37252190Srpaulo struct dl_list list; 38281806Srpaulo struct gas_query *gas; 39252190Srpaulo u8 addr[ETH_ALEN]; 40252190Srpaulo u8 dialog_token; 41252190Srpaulo u8 next_frag_id; 42252190Srpaulo unsigned int wait_comeback:1; 43252190Srpaulo unsigned int offchannel_tx_started:1; 44337817Scy unsigned int retry:1; 45346981Scy unsigned int wildcard_bssid:1; 46252190Srpaulo int freq; 47252190Srpaulo u16 status_code; 48281806Srpaulo struct wpabuf *req; 49252190Srpaulo struct wpabuf *adv_proto; 50252190Srpaulo struct wpabuf *resp; 51281806Srpaulo struct os_reltime last_oper; 52252190Srpaulo void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, 53252190Srpaulo enum gas_query_result result, 54252190Srpaulo const struct wpabuf *adv_proto, 55252190Srpaulo const struct wpabuf *resp, u16 status_code); 56252190Srpaulo void *ctx; 57346981Scy u8 sa[ETH_ALEN]; 58252190Srpaulo}; 59252190Srpaulo 60252190Srpaulo/** 61252190Srpaulo * struct gas_query - Internal GAS query data 62252190Srpaulo */ 63252190Srpaulostruct gas_query { 64252190Srpaulo struct wpa_supplicant *wpa_s; 65252190Srpaulo struct dl_list pending; /* struct gas_query_pending */ 66281806Srpaulo struct gas_query_pending *current; 67281806Srpaulo struct wpa_radio_work *work; 68346981Scy struct os_reltime last_mac_addr_rand; 69346981Scy int last_rand_sa_type; 70346981Scy u8 rand_addr[ETH_ALEN]; 71252190Srpaulo}; 72252190Srpaulo 73252190Srpaulo 74252190Srpaulostatic void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx); 75252190Srpaulostatic void gas_query_timeout(void *eloop_data, void *user_ctx); 76337817Scystatic void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx); 77337817Scystatic void gas_query_tx_initial_req(struct gas_query *gas, 78337817Scy struct gas_query_pending *query); 79337817Scystatic int gas_query_new_dialog_token(struct gas_query *gas, const u8 *dst); 80252190Srpaulo 81252190Srpaulo 82281806Srpaulostatic int ms_from_time(struct os_reltime *last) 83281806Srpaulo{ 84281806Srpaulo struct os_reltime now, res; 85281806Srpaulo 86281806Srpaulo os_get_reltime(&now); 87281806Srpaulo os_reltime_sub(&now, last, &res); 88281806Srpaulo return res.sec * 1000 + res.usec / 1000; 89281806Srpaulo} 90281806Srpaulo 91281806Srpaulo 92252190Srpaulo/** 93252190Srpaulo * gas_query_init - Initialize GAS query component 94252190Srpaulo * @wpa_s: Pointer to wpa_supplicant data 95252190Srpaulo * Returns: Pointer to GAS query data or %NULL on failure 96252190Srpaulo */ 97252190Srpaulostruct gas_query * gas_query_init(struct wpa_supplicant *wpa_s) 98252190Srpaulo{ 99252190Srpaulo struct gas_query *gas; 100252190Srpaulo 101252190Srpaulo gas = os_zalloc(sizeof(*gas)); 102252190Srpaulo if (gas == NULL) 103252190Srpaulo return NULL; 104252190Srpaulo 105252190Srpaulo gas->wpa_s = wpa_s; 106252190Srpaulo dl_list_init(&gas->pending); 107252190Srpaulo 108252190Srpaulo return gas; 109252190Srpaulo} 110252190Srpaulo 111252190Srpaulo 112281806Srpaulostatic const char * gas_result_txt(enum gas_query_result result) 113281806Srpaulo{ 114281806Srpaulo switch (result) { 115281806Srpaulo case GAS_QUERY_SUCCESS: 116281806Srpaulo return "SUCCESS"; 117281806Srpaulo case GAS_QUERY_FAILURE: 118281806Srpaulo return "FAILURE"; 119281806Srpaulo case GAS_QUERY_TIMEOUT: 120281806Srpaulo return "TIMEOUT"; 121281806Srpaulo case GAS_QUERY_PEER_ERROR: 122281806Srpaulo return "PEER_ERROR"; 123281806Srpaulo case GAS_QUERY_INTERNAL_ERROR: 124281806Srpaulo return "INTERNAL_ERROR"; 125346981Scy case GAS_QUERY_STOPPED: 126346981Scy return "STOPPED"; 127281806Srpaulo case GAS_QUERY_DELETED_AT_DEINIT: 128281806Srpaulo return "DELETED_AT_DEINIT"; 129281806Srpaulo } 130281806Srpaulo 131281806Srpaulo return "N/A"; 132281806Srpaulo} 133281806Srpaulo 134281806Srpaulo 135281806Srpaulostatic void gas_query_free(struct gas_query_pending *query, int del_list) 136281806Srpaulo{ 137281806Srpaulo struct gas_query *gas = query->gas; 138281806Srpaulo 139281806Srpaulo if (del_list) 140281806Srpaulo dl_list_del(&query->list); 141281806Srpaulo 142281806Srpaulo if (gas->work && gas->work->ctx == query) { 143281806Srpaulo radio_work_done(gas->work); 144281806Srpaulo gas->work = NULL; 145281806Srpaulo } 146281806Srpaulo 147281806Srpaulo wpabuf_free(query->req); 148281806Srpaulo wpabuf_free(query->adv_proto); 149281806Srpaulo wpabuf_free(query->resp); 150281806Srpaulo os_free(query); 151281806Srpaulo} 152281806Srpaulo 153281806Srpaulo 154252190Srpaulostatic void gas_query_done(struct gas_query *gas, 155252190Srpaulo struct gas_query_pending *query, 156252190Srpaulo enum gas_query_result result) 157252190Srpaulo{ 158281806Srpaulo wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR 159281806Srpaulo " dialog_token=%u freq=%d status_code=%u result=%s", 160281806Srpaulo MAC2STR(query->addr), query->dialog_token, query->freq, 161281806Srpaulo query->status_code, gas_result_txt(result)); 162281806Srpaulo if (gas->current == query) 163281806Srpaulo gas->current = NULL; 164252190Srpaulo if (query->offchannel_tx_started) 165252190Srpaulo offchannel_send_action_done(gas->wpa_s); 166252190Srpaulo eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); 167252190Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 168337817Scy eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query); 169252190Srpaulo dl_list_del(&query->list); 170252190Srpaulo query->cb(query->ctx, query->addr, query->dialog_token, result, 171252190Srpaulo query->adv_proto, query->resp, query->status_code); 172281806Srpaulo gas_query_free(query, 0); 173252190Srpaulo} 174252190Srpaulo 175252190Srpaulo 176252190Srpaulo/** 177252190Srpaulo * gas_query_deinit - Deinitialize GAS query component 178252190Srpaulo * @gas: GAS query data from gas_query_init() 179252190Srpaulo */ 180252190Srpaulovoid gas_query_deinit(struct gas_query *gas) 181252190Srpaulo{ 182252190Srpaulo struct gas_query_pending *query, *next; 183252190Srpaulo 184252190Srpaulo if (gas == NULL) 185252190Srpaulo return; 186252190Srpaulo 187252190Srpaulo dl_list_for_each_safe(query, next, &gas->pending, 188252190Srpaulo struct gas_query_pending, list) 189252190Srpaulo gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); 190252190Srpaulo 191252190Srpaulo os_free(gas); 192252190Srpaulo} 193252190Srpaulo 194252190Srpaulo 195252190Srpaulostatic struct gas_query_pending * 196252190Srpaulogas_query_get_pending(struct gas_query *gas, const u8 *addr, u8 dialog_token) 197252190Srpaulo{ 198252190Srpaulo struct gas_query_pending *q; 199252190Srpaulo dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { 200252190Srpaulo if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 && 201252190Srpaulo q->dialog_token == dialog_token) 202252190Srpaulo return q; 203252190Srpaulo } 204252190Srpaulo return NULL; 205252190Srpaulo} 206252190Srpaulo 207252190Srpaulo 208252190Srpaulostatic int gas_query_append(struct gas_query_pending *query, const u8 *data, 209252190Srpaulo size_t len) 210252190Srpaulo{ 211252190Srpaulo if (wpabuf_resize(&query->resp, len) < 0) { 212252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No memory to store the response"); 213252190Srpaulo return -1; 214252190Srpaulo } 215252190Srpaulo wpabuf_put_data(query->resp, data, len); 216252190Srpaulo return 0; 217252190Srpaulo} 218252190Srpaulo 219252190Srpaulo 220281806Srpaulostatic void gas_query_tx_status(struct wpa_supplicant *wpa_s, 221281806Srpaulo unsigned int freq, const u8 *dst, 222281806Srpaulo const u8 *src, const u8 *bssid, 223281806Srpaulo const u8 *data, size_t data_len, 224281806Srpaulo enum offchannel_send_action_result result) 225281806Srpaulo{ 226281806Srpaulo struct gas_query_pending *query; 227281806Srpaulo struct gas_query *gas = wpa_s->gas; 228281806Srpaulo int dur; 229281806Srpaulo 230281806Srpaulo if (gas->current == NULL) { 231281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst=" 232281806Srpaulo MACSTR " result=%d - no query in progress", 233281806Srpaulo freq, MAC2STR(dst), result); 234281806Srpaulo return; 235281806Srpaulo } 236281806Srpaulo 237281806Srpaulo query = gas->current; 238281806Srpaulo 239281806Srpaulo dur = ms_from_time(&query->last_oper); 240281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR 241281806Srpaulo " result=%d query=%p dialog_token=%u dur=%d ms", 242281806Srpaulo freq, MAC2STR(dst), result, query, query->dialog_token, dur); 243281806Srpaulo if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) { 244281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); 245281806Srpaulo return; 246281806Srpaulo } 247281806Srpaulo os_get_reltime(&query->last_oper); 248281806Srpaulo 249346981Scy if (result == OFFCHANNEL_SEND_ACTION_SUCCESS || 250346981Scy result == OFFCHANNEL_SEND_ACTION_NO_ACK) { 251281806Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 252346981Scy if (result == OFFCHANNEL_SEND_ACTION_NO_ACK) { 253346981Scy wpa_printf(MSG_DEBUG, "GAS: No ACK to GAS request"); 254346981Scy eloop_register_timeout(0, 250000, 255346981Scy gas_query_timeout, gas, query); 256346981Scy } else { 257346981Scy eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, 258346981Scy gas_query_timeout, gas, query); 259346981Scy } 260337817Scy if (query->wait_comeback && !query->retry) { 261337817Scy eloop_cancel_timeout(gas_query_rx_comeback_timeout, 262337817Scy gas, query); 263337817Scy eloop_register_timeout( 264337817Scy 0, (GAS_QUERY_WAIT_TIME_COMEBACK + 10) * 1000, 265337817Scy gas_query_rx_comeback_timeout, gas, query); 266337817Scy } 267281806Srpaulo } 268281806Srpaulo if (result == OFFCHANNEL_SEND_ACTION_FAILED) { 269281806Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 270281806Srpaulo eloop_register_timeout(0, 0, gas_query_timeout, gas, query); 271281806Srpaulo } 272281806Srpaulo} 273281806Srpaulo 274281806Srpaulo 275346981Scyint pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr) 276281806Srpaulo{ 277281806Srpaulo if (wpa_s->current_ssid == NULL || 278281806Srpaulo wpa_s->wpa_state < WPA_4WAY_HANDSHAKE || 279281806Srpaulo os_memcmp(addr, wpa_s->bssid, ETH_ALEN) != 0) 280281806Srpaulo return 0; 281281806Srpaulo return wpa_sm_pmf_enabled(wpa_s->wpa); 282281806Srpaulo} 283281806Srpaulo 284281806Srpaulo 285252190Srpaulostatic int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query, 286337817Scy struct wpabuf *req, unsigned int wait_time) 287252190Srpaulo{ 288281806Srpaulo int res, prot = pmf_in_use(gas->wpa_s, query->addr); 289337817Scy const u8 *bssid; 290337817Scy const u8 wildcard_bssid[ETH_ALEN] = { 291337817Scy 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 292337817Scy }; 293281806Srpaulo 294252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u " 295346981Scy "freq=%d prot=%d using src addr " MACSTR, 296346981Scy MAC2STR(query->addr), (unsigned int) wpabuf_len(req), 297346981Scy query->freq, prot, MAC2STR(query->sa)); 298281806Srpaulo if (prot) { 299281806Srpaulo u8 *categ = wpabuf_mhead_u8(req); 300281806Srpaulo *categ = WLAN_ACTION_PROTECTED_DUAL; 301281806Srpaulo } 302281806Srpaulo os_get_reltime(&query->last_oper); 303281806Srpaulo if (gas->wpa_s->max_remain_on_chan && 304281806Srpaulo wait_time > gas->wpa_s->max_remain_on_chan) 305281806Srpaulo wait_time = gas->wpa_s->max_remain_on_chan; 306346981Scy if (!query->wildcard_bssid && 307346981Scy (!gas->wpa_s->conf->gas_address3 || 308346981Scy (gas->wpa_s->current_ssid && 309346981Scy gas->wpa_s->wpa_state >= WPA_ASSOCIATED && 310346981Scy os_memcmp(query->addr, gas->wpa_s->bssid, ETH_ALEN) == 0))) 311337817Scy bssid = query->addr; 312337817Scy else 313337817Scy bssid = wildcard_bssid; 314346981Scy 315252190Srpaulo res = offchannel_send_action(gas->wpa_s, query->freq, query->addr, 316346981Scy query->sa, bssid, wpabuf_head(req), 317346981Scy wpabuf_len(req), wait_time, 318346981Scy gas_query_tx_status, 0); 319346981Scy 320252190Srpaulo if (res == 0) 321252190Srpaulo query->offchannel_tx_started = 1; 322252190Srpaulo return res; 323252190Srpaulo} 324252190Srpaulo 325252190Srpaulo 326252190Srpaulostatic void gas_query_tx_comeback_req(struct gas_query *gas, 327252190Srpaulo struct gas_query_pending *query) 328252190Srpaulo{ 329252190Srpaulo struct wpabuf *req; 330337817Scy unsigned int wait_time; 331252190Srpaulo 332252190Srpaulo req = gas_build_comeback_req(query->dialog_token); 333252190Srpaulo if (req == NULL) { 334252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 335252190Srpaulo return; 336252190Srpaulo } 337252190Srpaulo 338337817Scy wait_time = (query->retry || !query->offchannel_tx_started) ? 339337817Scy GAS_QUERY_WAIT_TIME_INITIAL : GAS_QUERY_WAIT_TIME_COMEBACK; 340337817Scy 341337817Scy if (gas_query_tx(gas, query, req, wait_time) < 0) { 342252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " 343252190Srpaulo MACSTR, MAC2STR(query->addr)); 344252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 345252190Srpaulo } 346252190Srpaulo 347252190Srpaulo wpabuf_free(req); 348252190Srpaulo} 349252190Srpaulo 350252190Srpaulo 351337817Scystatic void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx) 352337817Scy{ 353337817Scy struct gas_query *gas = eloop_data; 354337817Scy struct gas_query_pending *query = user_ctx; 355337817Scy int dialog_token; 356337817Scy 357337817Scy wpa_printf(MSG_DEBUG, 358337817Scy "GAS: No response to comeback request received (retry=%u)", 359337817Scy query->retry); 360337817Scy if (gas->current != query || query->retry) 361337817Scy return; 362337817Scy dialog_token = gas_query_new_dialog_token(gas, query->addr); 363337817Scy if (dialog_token < 0) 364337817Scy return; 365337817Scy wpa_printf(MSG_DEBUG, 366337817Scy "GAS: Retry GAS query due to comeback response timeout"); 367337817Scy query->retry = 1; 368337817Scy query->dialog_token = dialog_token; 369337817Scy *(wpabuf_mhead_u8(query->req) + 2) = dialog_token; 370337817Scy query->wait_comeback = 0; 371337817Scy query->next_frag_id = 0; 372337817Scy wpabuf_free(query->adv_proto); 373337817Scy query->adv_proto = NULL; 374337817Scy eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); 375337817Scy eloop_cancel_timeout(gas_query_timeout, gas, query); 376337817Scy gas_query_tx_initial_req(gas, query); 377337817Scy} 378337817Scy 379337817Scy 380252190Srpaulostatic void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx) 381252190Srpaulo{ 382252190Srpaulo struct gas_query *gas = eloop_data; 383252190Srpaulo struct gas_query_pending *query = user_ctx; 384252190Srpaulo 385252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR, 386252190Srpaulo MAC2STR(query->addr)); 387252190Srpaulo gas_query_tx_comeback_req(gas, query); 388252190Srpaulo} 389252190Srpaulo 390252190Srpaulo 391252190Srpaulostatic void gas_query_tx_comeback_req_delay(struct gas_query *gas, 392252190Srpaulo struct gas_query_pending *query, 393252190Srpaulo u16 comeback_delay) 394252190Srpaulo{ 395252190Srpaulo unsigned int secs, usecs; 396252190Srpaulo 397337817Scy if (comeback_delay > 1 && query->offchannel_tx_started) { 398337817Scy offchannel_send_action_done(gas->wpa_s); 399337817Scy query->offchannel_tx_started = 0; 400337817Scy } 401337817Scy 402252190Srpaulo secs = (comeback_delay * 1024) / 1000000; 403252190Srpaulo usecs = comeback_delay * 1024 - secs * 1000000; 404252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR 405252190Srpaulo " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs); 406252190Srpaulo eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); 407252190Srpaulo eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout, 408252190Srpaulo gas, query); 409252190Srpaulo} 410252190Srpaulo 411252190Srpaulo 412252190Srpaulostatic void gas_query_rx_initial(struct gas_query *gas, 413252190Srpaulo struct gas_query_pending *query, 414252190Srpaulo const u8 *adv_proto, const u8 *resp, 415252190Srpaulo size_t len, u16 comeback_delay) 416252190Srpaulo{ 417252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Received initial response from " 418252190Srpaulo MACSTR " (dialog_token=%u comeback_delay=%u)", 419252190Srpaulo MAC2STR(query->addr), query->dialog_token, comeback_delay); 420252190Srpaulo 421252190Srpaulo query->adv_proto = wpabuf_alloc_copy(adv_proto, 2 + adv_proto[1]); 422252190Srpaulo if (query->adv_proto == NULL) { 423252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 424252190Srpaulo return; 425252190Srpaulo } 426252190Srpaulo 427252190Srpaulo if (comeback_delay) { 428346981Scy eloop_cancel_timeout(gas_query_timeout, gas, query); 429252190Srpaulo query->wait_comeback = 1; 430252190Srpaulo gas_query_tx_comeback_req_delay(gas, query, comeback_delay); 431252190Srpaulo return; 432252190Srpaulo } 433252190Srpaulo 434252190Srpaulo /* Query was completed without comeback mechanism */ 435252190Srpaulo if (gas_query_append(query, resp, len) < 0) { 436252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 437252190Srpaulo return; 438252190Srpaulo } 439252190Srpaulo 440252190Srpaulo gas_query_done(gas, query, GAS_QUERY_SUCCESS); 441252190Srpaulo} 442252190Srpaulo 443252190Srpaulo 444252190Srpaulostatic void gas_query_rx_comeback(struct gas_query *gas, 445252190Srpaulo struct gas_query_pending *query, 446252190Srpaulo const u8 *adv_proto, const u8 *resp, 447252190Srpaulo size_t len, u8 frag_id, u8 more_frags, 448252190Srpaulo u16 comeback_delay) 449252190Srpaulo{ 450252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Received comeback response from " 451252190Srpaulo MACSTR " (dialog_token=%u frag_id=%u more_frags=%u " 452252190Srpaulo "comeback_delay=%u)", 453252190Srpaulo MAC2STR(query->addr), query->dialog_token, frag_id, 454252190Srpaulo more_frags, comeback_delay); 455337817Scy eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query); 456252190Srpaulo 457252190Srpaulo if ((size_t) 2 + adv_proto[1] != wpabuf_len(query->adv_proto) || 458252190Srpaulo os_memcmp(adv_proto, wpabuf_head(query->adv_proto), 459252190Srpaulo wpabuf_len(query->adv_proto)) != 0) { 460252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed " 461252190Srpaulo "between initial and comeback response from " 462252190Srpaulo MACSTR, MAC2STR(query->addr)); 463252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 464252190Srpaulo return; 465252190Srpaulo } 466252190Srpaulo 467252190Srpaulo if (comeback_delay) { 468252190Srpaulo if (frag_id) { 469252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response " 470252190Srpaulo "with non-zero frag_id and comeback_delay " 471252190Srpaulo "from " MACSTR, MAC2STR(query->addr)); 472252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 473252190Srpaulo return; 474252190Srpaulo } 475252190Srpaulo gas_query_tx_comeback_req_delay(gas, query, comeback_delay); 476252190Srpaulo return; 477252190Srpaulo } 478252190Srpaulo 479252190Srpaulo if (frag_id != query->next_frag_id) { 480252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response " 481252190Srpaulo "from " MACSTR, MAC2STR(query->addr)); 482281806Srpaulo if (frag_id + 1 == query->next_frag_id) { 483281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible " 484281806Srpaulo "retry of previous fragment"); 485281806Srpaulo return; 486281806Srpaulo } 487252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 488252190Srpaulo return; 489252190Srpaulo } 490252190Srpaulo query->next_frag_id++; 491252190Srpaulo 492252190Srpaulo if (gas_query_append(query, resp, len) < 0) { 493252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 494252190Srpaulo return; 495252190Srpaulo } 496252190Srpaulo 497252190Srpaulo if (more_frags) { 498252190Srpaulo gas_query_tx_comeback_req(gas, query); 499252190Srpaulo return; 500252190Srpaulo } 501252190Srpaulo 502252190Srpaulo gas_query_done(gas, query, GAS_QUERY_SUCCESS); 503252190Srpaulo} 504252190Srpaulo 505252190Srpaulo 506252190Srpaulo/** 507281806Srpaulo * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame 508252190Srpaulo * @gas: GAS query data from gas_query_init() 509252190Srpaulo * @da: Destination MAC address of the Action frame 510252190Srpaulo * @sa: Source MAC address of the Action frame 511252190Srpaulo * @bssid: BSSID of the Action frame 512281806Srpaulo * @categ: Category of the Action frame 513252190Srpaulo * @data: Payload of the Action frame 514252190Srpaulo * @len: Length of @data 515252190Srpaulo * @freq: Frequency (in MHz) on which the frame was received 516252190Srpaulo * Returns: 0 if the Public Action frame was a GAS frame or -1 if not 517252190Srpaulo */ 518252190Srpauloint gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, 519281806Srpaulo const u8 *bssid, u8 categ, const u8 *data, size_t len, 520281806Srpaulo int freq) 521252190Srpaulo{ 522252190Srpaulo struct gas_query_pending *query; 523252190Srpaulo u8 action, dialog_token, frag_id = 0, more_frags = 0; 524252190Srpaulo u16 comeback_delay, resp_len; 525252190Srpaulo const u8 *pos, *adv_proto; 526281806Srpaulo int prot, pmf; 527281806Srpaulo unsigned int left; 528252190Srpaulo 529252190Srpaulo if (gas == NULL || len < 4) 530252190Srpaulo return -1; 531252190Srpaulo 532337817Scy pos = data; 533337817Scy action = *pos++; 534337817Scy dialog_token = *pos++; 535337817Scy 536337817Scy if (action != WLAN_PA_GAS_INITIAL_RESP && 537337817Scy action != WLAN_PA_GAS_COMEBACK_RESP) 538337817Scy return -1; /* Not a GAS response */ 539337817Scy 540281806Srpaulo prot = categ == WLAN_ACTION_PROTECTED_DUAL; 541337817Scy pmf = pmf_in_use(gas->wpa_s, sa); 542281806Srpaulo if (prot && !pmf) { 543281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled"); 544281806Srpaulo return 0; 545281806Srpaulo } 546281806Srpaulo if (!prot && pmf) { 547281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled"); 548281806Srpaulo return 0; 549281806Srpaulo } 550281806Srpaulo 551252190Srpaulo query = gas_query_get_pending(gas, sa, dialog_token); 552252190Srpaulo if (query == NULL) { 553252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR 554252190Srpaulo " dialog token %u", MAC2STR(sa), dialog_token); 555252190Srpaulo return -1; 556252190Srpaulo } 557252190Srpaulo 558281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR, 559281806Srpaulo ms_from_time(&query->last_oper), MAC2STR(sa)); 560281806Srpaulo 561252190Srpaulo if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) { 562252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from " 563252190Srpaulo MACSTR " dialog token %u when waiting for comeback " 564252190Srpaulo "response", MAC2STR(sa), dialog_token); 565252190Srpaulo return 0; 566252190Srpaulo } 567252190Srpaulo 568252190Srpaulo if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) { 569252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from " 570252190Srpaulo MACSTR " dialog token %u when waiting for initial " 571252190Srpaulo "response", MAC2STR(sa), dialog_token); 572252190Srpaulo return 0; 573252190Srpaulo } 574252190Srpaulo 575252190Srpaulo query->status_code = WPA_GET_LE16(pos); 576252190Srpaulo pos += 2; 577252190Srpaulo 578281806Srpaulo if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING && 579281806Srpaulo action == WLAN_PA_GAS_COMEBACK_RESP) { 580281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response"); 581281806Srpaulo } else if (query->status_code != WLAN_STATUS_SUCCESS) { 582252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token " 583252190Srpaulo "%u failed - status code %u", 584252190Srpaulo MAC2STR(sa), dialog_token, query->status_code); 585252190Srpaulo gas_query_done(gas, query, GAS_QUERY_FAILURE); 586252190Srpaulo return 0; 587252190Srpaulo } 588252190Srpaulo 589252190Srpaulo if (action == WLAN_PA_GAS_COMEBACK_RESP) { 590252190Srpaulo if (pos + 1 > data + len) 591252190Srpaulo return 0; 592252190Srpaulo frag_id = *pos & 0x7f; 593252190Srpaulo more_frags = (*pos & 0x80) >> 7; 594252190Srpaulo pos++; 595252190Srpaulo } 596252190Srpaulo 597252190Srpaulo /* Comeback Delay */ 598252190Srpaulo if (pos + 2 > data + len) 599252190Srpaulo return 0; 600252190Srpaulo comeback_delay = WPA_GET_LE16(pos); 601252190Srpaulo pos += 2; 602252190Srpaulo 603252190Srpaulo /* Advertisement Protocol element */ 604252190Srpaulo if (pos + 2 > data + len || pos + 2 + pos[1] > data + len) { 605252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement " 606252190Srpaulo "Protocol element in the response from " MACSTR, 607252190Srpaulo MAC2STR(sa)); 608252190Srpaulo return 0; 609252190Srpaulo } 610252190Srpaulo 611252190Srpaulo if (*pos != WLAN_EID_ADV_PROTO) { 612252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement " 613252190Srpaulo "Protocol element ID %u in response from " MACSTR, 614252190Srpaulo *pos, MAC2STR(sa)); 615252190Srpaulo return 0; 616252190Srpaulo } 617252190Srpaulo 618252190Srpaulo adv_proto = pos; 619252190Srpaulo pos += 2 + pos[1]; 620252190Srpaulo 621252190Srpaulo /* Query Response Length */ 622252190Srpaulo if (pos + 2 > data + len) { 623252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length"); 624252190Srpaulo return 0; 625252190Srpaulo } 626252190Srpaulo resp_len = WPA_GET_LE16(pos); 627252190Srpaulo pos += 2; 628252190Srpaulo 629281806Srpaulo left = data + len - pos; 630281806Srpaulo if (resp_len > left) { 631252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in " 632252190Srpaulo "response from " MACSTR, MAC2STR(sa)); 633252190Srpaulo return 0; 634252190Srpaulo } 635252190Srpaulo 636281806Srpaulo if (resp_len < left) { 637252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data " 638252190Srpaulo "after Query Response from " MACSTR, 639281806Srpaulo left - resp_len, MAC2STR(sa)); 640252190Srpaulo } 641252190Srpaulo 642252190Srpaulo if (action == WLAN_PA_GAS_COMEBACK_RESP) 643252190Srpaulo gas_query_rx_comeback(gas, query, adv_proto, pos, resp_len, 644252190Srpaulo frag_id, more_frags, comeback_delay); 645252190Srpaulo else 646252190Srpaulo gas_query_rx_initial(gas, query, adv_proto, pos, resp_len, 647252190Srpaulo comeback_delay); 648252190Srpaulo 649252190Srpaulo return 0; 650252190Srpaulo} 651252190Srpaulo 652252190Srpaulo 653252190Srpaulostatic void gas_query_timeout(void *eloop_data, void *user_ctx) 654252190Srpaulo{ 655252190Srpaulo struct gas_query *gas = eloop_data; 656252190Srpaulo struct gas_query_pending *query = user_ctx; 657252190Srpaulo 658281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR 659281806Srpaulo " dialog token %u", 660281806Srpaulo MAC2STR(query->addr), query->dialog_token); 661252190Srpaulo gas_query_done(gas, query, GAS_QUERY_TIMEOUT); 662252190Srpaulo} 663252190Srpaulo 664252190Srpaulo 665252190Srpaulostatic int gas_query_dialog_token_available(struct gas_query *gas, 666252190Srpaulo const u8 *dst, u8 dialog_token) 667252190Srpaulo{ 668252190Srpaulo struct gas_query_pending *q; 669252190Srpaulo dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { 670252190Srpaulo if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 && 671252190Srpaulo dialog_token == q->dialog_token) 672252190Srpaulo return 0; 673252190Srpaulo } 674252190Srpaulo 675252190Srpaulo return 1; 676252190Srpaulo} 677252190Srpaulo 678252190Srpaulo 679281806Srpaulostatic void gas_query_start_cb(struct wpa_radio_work *work, int deinit) 680281806Srpaulo{ 681281806Srpaulo struct gas_query_pending *query = work->ctx; 682281806Srpaulo struct gas_query *gas = query->gas; 683281806Srpaulo struct wpa_supplicant *wpa_s = gas->wpa_s; 684281806Srpaulo 685281806Srpaulo if (deinit) { 686281806Srpaulo if (work->started) { 687281806Srpaulo gas->work = NULL; 688281806Srpaulo gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); 689281806Srpaulo return; 690281806Srpaulo } 691281806Srpaulo 692281806Srpaulo gas_query_free(query, 1); 693281806Srpaulo return; 694281806Srpaulo } 695281806Srpaulo 696281806Srpaulo if (wpas_update_random_addr_disassoc(wpa_s) < 0) { 697281806Srpaulo wpa_msg(wpa_s, MSG_INFO, 698281806Srpaulo "Failed to assign random MAC address for GAS"); 699281806Srpaulo gas_query_free(query, 1); 700281806Srpaulo radio_work_done(work); 701281806Srpaulo return; 702281806Srpaulo } 703281806Srpaulo 704281806Srpaulo gas->work = work; 705337817Scy gas_query_tx_initial_req(gas, query); 706337817Scy} 707281806Srpaulo 708337817Scy 709337817Scystatic void gas_query_tx_initial_req(struct gas_query *gas, 710337817Scy struct gas_query_pending *query) 711337817Scy{ 712337817Scy if (gas_query_tx(gas, query, query->req, 713337817Scy GAS_QUERY_WAIT_TIME_INITIAL) < 0) { 714281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " 715281806Srpaulo MACSTR, MAC2STR(query->addr)); 716337817Scy gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 717281806Srpaulo return; 718281806Srpaulo } 719281806Srpaulo gas->current = query; 720281806Srpaulo 721281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u", 722281806Srpaulo query->dialog_token); 723281806Srpaulo eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, 724281806Srpaulo gas_query_timeout, gas, query); 725337817Scy} 726281806Srpaulo 727337817Scy 728337817Scystatic int gas_query_new_dialog_token(struct gas_query *gas, const u8 *dst) 729337817Scy{ 730337817Scy static int next_start = 0; 731337817Scy int dialog_token; 732337817Scy 733337817Scy for (dialog_token = 0; dialog_token < 256; dialog_token++) { 734337817Scy if (gas_query_dialog_token_available( 735337817Scy gas, dst, (next_start + dialog_token) % 256)) 736337817Scy break; 737337817Scy } 738337817Scy if (dialog_token == 256) 739337817Scy return -1; /* Too many pending queries */ 740337817Scy dialog_token = (next_start + dialog_token) % 256; 741337817Scy next_start = (dialog_token + 1) % 256; 742337817Scy return dialog_token; 743281806Srpaulo} 744281806Srpaulo 745281806Srpaulo 746346981Scystatic int gas_query_set_sa(struct gas_query *gas, 747346981Scy struct gas_query_pending *query) 748346981Scy{ 749346981Scy struct wpa_supplicant *wpa_s = gas->wpa_s; 750346981Scy struct os_reltime now; 751346981Scy 752346981Scy if (!wpa_s->conf->gas_rand_mac_addr || 753346981Scy !(wpa_s->current_bss ? 754346981Scy (wpa_s->drv_flags & 755346981Scy WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA_CONNECTED) : 756346981Scy (wpa_s->drv_flags & WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA))) { 757346981Scy /* Use own MAC address as the transmitter address */ 758346981Scy os_memcpy(query->sa, wpa_s->own_addr, ETH_ALEN); 759346981Scy return 0; 760346981Scy } 761346981Scy 762346981Scy os_get_reltime(&now); 763346981Scy 764346981Scy if (wpa_s->conf->gas_rand_mac_addr == gas->last_rand_sa_type && 765346981Scy gas->last_mac_addr_rand.sec != 0 && 766346981Scy !os_reltime_expired(&now, &gas->last_mac_addr_rand, 767346981Scy wpa_s->conf->gas_rand_addr_lifetime)) { 768346981Scy wpa_printf(MSG_DEBUG, 769346981Scy "GAS: Use the previously selected random transmitter address " 770346981Scy MACSTR, MAC2STR(gas->rand_addr)); 771346981Scy os_memcpy(query->sa, gas->rand_addr, ETH_ALEN); 772346981Scy return 0; 773346981Scy } 774346981Scy 775346981Scy if (wpa_s->conf->gas_rand_mac_addr == 1 && 776346981Scy random_mac_addr(gas->rand_addr) < 0) { 777346981Scy wpa_printf(MSG_ERROR, "GAS: Failed to get random address"); 778346981Scy return -1; 779346981Scy } 780346981Scy 781346981Scy if (wpa_s->conf->gas_rand_mac_addr == 2 && 782346981Scy random_mac_addr_keep_oui(gas->rand_addr) < 0) { 783346981Scy wpa_printf(MSG_ERROR, 784346981Scy "GAS: Failed to get random address with same OUI"); 785346981Scy return -1; 786346981Scy } 787346981Scy 788346981Scy wpa_printf(MSG_DEBUG, "GAS: Use a new random transmitter address " 789346981Scy MACSTR, MAC2STR(gas->rand_addr)); 790346981Scy os_memcpy(query->sa, gas->rand_addr, ETH_ALEN); 791346981Scy os_get_reltime(&gas->last_mac_addr_rand); 792346981Scy gas->last_rand_sa_type = wpa_s->conf->gas_rand_mac_addr; 793346981Scy 794346981Scy return 0; 795346981Scy} 796346981Scy 797346981Scy 798252190Srpaulo/** 799252190Srpaulo * gas_query_req - Request a GAS query 800252190Srpaulo * @gas: GAS query data from gas_query_init() 801252190Srpaulo * @dst: Destination MAC address for the query 802252190Srpaulo * @freq: Frequency (in MHz) for the channel on which to send the query 803281806Srpaulo * @req: GAS query payload (to be freed by gas_query module in case of success 804281806Srpaulo * return) 805252190Srpaulo * @cb: Callback function for reporting GAS query result and response 806252190Srpaulo * @ctx: Context pointer to use with the @cb call 807252190Srpaulo * Returns: dialog token (>= 0) on success or -1 on failure 808252190Srpaulo */ 809252190Srpauloint gas_query_req(struct gas_query *gas, const u8 *dst, int freq, 810346981Scy int wildcard_bssid, struct wpabuf *req, 811252190Srpaulo void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, 812252190Srpaulo enum gas_query_result result, 813252190Srpaulo const struct wpabuf *adv_proto, 814252190Srpaulo const struct wpabuf *resp, u16 status_code), 815252190Srpaulo void *ctx) 816252190Srpaulo{ 817252190Srpaulo struct gas_query_pending *query; 818252190Srpaulo int dialog_token; 819252190Srpaulo 820252190Srpaulo if (wpabuf_len(req) < 3) 821252190Srpaulo return -1; 822252190Srpaulo 823337817Scy dialog_token = gas_query_new_dialog_token(gas, dst); 824337817Scy if (dialog_token < 0) 825337817Scy return -1; 826252190Srpaulo 827252190Srpaulo query = os_zalloc(sizeof(*query)); 828252190Srpaulo if (query == NULL) 829252190Srpaulo return -1; 830252190Srpaulo 831281806Srpaulo query->gas = gas; 832346981Scy if (gas_query_set_sa(gas, query)) { 833346981Scy os_free(query); 834346981Scy return -1; 835346981Scy } 836252190Srpaulo os_memcpy(query->addr, dst, ETH_ALEN); 837252190Srpaulo query->dialog_token = dialog_token; 838346981Scy query->wildcard_bssid = !!wildcard_bssid; 839252190Srpaulo query->freq = freq; 840252190Srpaulo query->cb = cb; 841252190Srpaulo query->ctx = ctx; 842281806Srpaulo query->req = req; 843252190Srpaulo dl_list_add(&gas->pending, &query->list); 844252190Srpaulo 845252190Srpaulo *(wpabuf_mhead_u8(req) + 2) = dialog_token; 846252190Srpaulo 847281806Srpaulo wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_START "addr=" MACSTR 848281806Srpaulo " dialog_token=%u freq=%d", 849281806Srpaulo MAC2STR(query->addr), query->dialog_token, query->freq); 850281806Srpaulo 851281806Srpaulo if (radio_add_work(gas->wpa_s, freq, "gas-query", 0, gas_query_start_cb, 852281806Srpaulo query) < 0) { 853337817Scy query->req = NULL; /* caller will free this in error case */ 854281806Srpaulo gas_query_free(query, 1); 855252190Srpaulo return -1; 856252190Srpaulo } 857252190Srpaulo 858252190Srpaulo return dialog_token; 859252190Srpaulo} 860346981Scy 861346981Scy 862346981Scyint gas_query_stop(struct gas_query *gas, u8 dialog_token) 863346981Scy{ 864346981Scy struct gas_query_pending *query; 865346981Scy 866346981Scy dl_list_for_each(query, &gas->pending, struct gas_query_pending, list) { 867346981Scy if (query->dialog_token == dialog_token) { 868346981Scy if (!gas->work) { 869346981Scy /* The pending radio work has not yet been 870346981Scy * started, but the pending entry has a 871346981Scy * reference to the soon to be freed query. 872346981Scy * Need to remove that radio work now to avoid 873346981Scy * leaving behind a reference to freed memory. 874346981Scy */ 875346981Scy radio_remove_pending_work(gas->wpa_s, query); 876346981Scy } 877346981Scy gas_query_done(gas, query, GAS_QUERY_STOPPED); 878346981Scy return 0; 879346981Scy } 880346981Scy } 881346981Scy 882346981Scy return -1; 883346981Scy} 884