gas_query.c revision 281806
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" 20252190Srpaulo#include "driver_i.h" 21252190Srpaulo#include "offchannel.h" 22252190Srpaulo#include "gas_query.h" 23252190Srpaulo 24252190Srpaulo 25252190Srpaulo/** GAS query timeout in seconds */ 26281806Srpaulo#define GAS_QUERY_TIMEOUT_PERIOD 2 27252190Srpaulo 28252190Srpaulo 29252190Srpaulo/** 30252190Srpaulo * struct gas_query_pending - Pending GAS query 31252190Srpaulo */ 32252190Srpaulostruct gas_query_pending { 33252190Srpaulo struct dl_list list; 34281806Srpaulo struct gas_query *gas; 35252190Srpaulo u8 addr[ETH_ALEN]; 36252190Srpaulo u8 dialog_token; 37252190Srpaulo u8 next_frag_id; 38252190Srpaulo unsigned int wait_comeback:1; 39252190Srpaulo unsigned int offchannel_tx_started:1; 40252190Srpaulo int freq; 41252190Srpaulo u16 status_code; 42281806Srpaulo struct wpabuf *req; 43252190Srpaulo struct wpabuf *adv_proto; 44252190Srpaulo struct wpabuf *resp; 45281806Srpaulo struct os_reltime last_oper; 46252190Srpaulo void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, 47252190Srpaulo enum gas_query_result result, 48252190Srpaulo const struct wpabuf *adv_proto, 49252190Srpaulo const struct wpabuf *resp, u16 status_code); 50252190Srpaulo void *ctx; 51252190Srpaulo}; 52252190Srpaulo 53252190Srpaulo/** 54252190Srpaulo * struct gas_query - Internal GAS query data 55252190Srpaulo */ 56252190Srpaulostruct gas_query { 57252190Srpaulo struct wpa_supplicant *wpa_s; 58252190Srpaulo struct dl_list pending; /* struct gas_query_pending */ 59281806Srpaulo struct gas_query_pending *current; 60281806Srpaulo struct wpa_radio_work *work; 61252190Srpaulo}; 62252190Srpaulo 63252190Srpaulo 64252190Srpaulostatic void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx); 65252190Srpaulostatic void gas_query_timeout(void *eloop_data, void *user_ctx); 66252190Srpaulo 67252190Srpaulo 68281806Srpaulostatic int ms_from_time(struct os_reltime *last) 69281806Srpaulo{ 70281806Srpaulo struct os_reltime now, res; 71281806Srpaulo 72281806Srpaulo os_get_reltime(&now); 73281806Srpaulo os_reltime_sub(&now, last, &res); 74281806Srpaulo return res.sec * 1000 + res.usec / 1000; 75281806Srpaulo} 76281806Srpaulo 77281806Srpaulo 78252190Srpaulo/** 79252190Srpaulo * gas_query_init - Initialize GAS query component 80252190Srpaulo * @wpa_s: Pointer to wpa_supplicant data 81252190Srpaulo * Returns: Pointer to GAS query data or %NULL on failure 82252190Srpaulo */ 83252190Srpaulostruct gas_query * gas_query_init(struct wpa_supplicant *wpa_s) 84252190Srpaulo{ 85252190Srpaulo struct gas_query *gas; 86252190Srpaulo 87252190Srpaulo gas = os_zalloc(sizeof(*gas)); 88252190Srpaulo if (gas == NULL) 89252190Srpaulo return NULL; 90252190Srpaulo 91252190Srpaulo gas->wpa_s = wpa_s; 92252190Srpaulo dl_list_init(&gas->pending); 93252190Srpaulo 94252190Srpaulo return gas; 95252190Srpaulo} 96252190Srpaulo 97252190Srpaulo 98281806Srpaulostatic const char * gas_result_txt(enum gas_query_result result) 99281806Srpaulo{ 100281806Srpaulo switch (result) { 101281806Srpaulo case GAS_QUERY_SUCCESS: 102281806Srpaulo return "SUCCESS"; 103281806Srpaulo case GAS_QUERY_FAILURE: 104281806Srpaulo return "FAILURE"; 105281806Srpaulo case GAS_QUERY_TIMEOUT: 106281806Srpaulo return "TIMEOUT"; 107281806Srpaulo case GAS_QUERY_PEER_ERROR: 108281806Srpaulo return "PEER_ERROR"; 109281806Srpaulo case GAS_QUERY_INTERNAL_ERROR: 110281806Srpaulo return "INTERNAL_ERROR"; 111281806Srpaulo case GAS_QUERY_CANCELLED: 112281806Srpaulo return "CANCELLED"; 113281806Srpaulo case GAS_QUERY_DELETED_AT_DEINIT: 114281806Srpaulo return "DELETED_AT_DEINIT"; 115281806Srpaulo } 116281806Srpaulo 117281806Srpaulo return "N/A"; 118281806Srpaulo} 119281806Srpaulo 120281806Srpaulo 121281806Srpaulostatic void gas_query_free(struct gas_query_pending *query, int del_list) 122281806Srpaulo{ 123281806Srpaulo struct gas_query *gas = query->gas; 124281806Srpaulo 125281806Srpaulo if (del_list) 126281806Srpaulo dl_list_del(&query->list); 127281806Srpaulo 128281806Srpaulo if (gas->work && gas->work->ctx == query) { 129281806Srpaulo radio_work_done(gas->work); 130281806Srpaulo gas->work = NULL; 131281806Srpaulo } 132281806Srpaulo 133281806Srpaulo wpabuf_free(query->req); 134281806Srpaulo wpabuf_free(query->adv_proto); 135281806Srpaulo wpabuf_free(query->resp); 136281806Srpaulo os_free(query); 137281806Srpaulo} 138281806Srpaulo 139281806Srpaulo 140252190Srpaulostatic void gas_query_done(struct gas_query *gas, 141252190Srpaulo struct gas_query_pending *query, 142252190Srpaulo enum gas_query_result result) 143252190Srpaulo{ 144281806Srpaulo wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR 145281806Srpaulo " dialog_token=%u freq=%d status_code=%u result=%s", 146281806Srpaulo MAC2STR(query->addr), query->dialog_token, query->freq, 147281806Srpaulo query->status_code, gas_result_txt(result)); 148281806Srpaulo if (gas->current == query) 149281806Srpaulo gas->current = NULL; 150252190Srpaulo if (query->offchannel_tx_started) 151252190Srpaulo offchannel_send_action_done(gas->wpa_s); 152252190Srpaulo eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); 153252190Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 154252190Srpaulo dl_list_del(&query->list); 155252190Srpaulo query->cb(query->ctx, query->addr, query->dialog_token, result, 156252190Srpaulo query->adv_proto, query->resp, query->status_code); 157281806Srpaulo gas_query_free(query, 0); 158252190Srpaulo} 159252190Srpaulo 160252190Srpaulo 161252190Srpaulo/** 162252190Srpaulo * gas_query_deinit - Deinitialize GAS query component 163252190Srpaulo * @gas: GAS query data from gas_query_init() 164252190Srpaulo */ 165252190Srpaulovoid gas_query_deinit(struct gas_query *gas) 166252190Srpaulo{ 167252190Srpaulo struct gas_query_pending *query, *next; 168252190Srpaulo 169252190Srpaulo if (gas == NULL) 170252190Srpaulo return; 171252190Srpaulo 172252190Srpaulo dl_list_for_each_safe(query, next, &gas->pending, 173252190Srpaulo struct gas_query_pending, list) 174252190Srpaulo gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); 175252190Srpaulo 176252190Srpaulo os_free(gas); 177252190Srpaulo} 178252190Srpaulo 179252190Srpaulo 180252190Srpaulostatic struct gas_query_pending * 181252190Srpaulogas_query_get_pending(struct gas_query *gas, const u8 *addr, u8 dialog_token) 182252190Srpaulo{ 183252190Srpaulo struct gas_query_pending *q; 184252190Srpaulo dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { 185252190Srpaulo if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 && 186252190Srpaulo q->dialog_token == dialog_token) 187252190Srpaulo return q; 188252190Srpaulo } 189252190Srpaulo return NULL; 190252190Srpaulo} 191252190Srpaulo 192252190Srpaulo 193252190Srpaulostatic int gas_query_append(struct gas_query_pending *query, const u8 *data, 194252190Srpaulo size_t len) 195252190Srpaulo{ 196252190Srpaulo if (wpabuf_resize(&query->resp, len) < 0) { 197252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No memory to store the response"); 198252190Srpaulo return -1; 199252190Srpaulo } 200252190Srpaulo wpabuf_put_data(query->resp, data, len); 201252190Srpaulo return 0; 202252190Srpaulo} 203252190Srpaulo 204252190Srpaulo 205281806Srpaulostatic void gas_query_tx_status(struct wpa_supplicant *wpa_s, 206281806Srpaulo unsigned int freq, const u8 *dst, 207281806Srpaulo const u8 *src, const u8 *bssid, 208281806Srpaulo const u8 *data, size_t data_len, 209281806Srpaulo enum offchannel_send_action_result result) 210281806Srpaulo{ 211281806Srpaulo struct gas_query_pending *query; 212281806Srpaulo struct gas_query *gas = wpa_s->gas; 213281806Srpaulo int dur; 214281806Srpaulo 215281806Srpaulo if (gas->current == NULL) { 216281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst=" 217281806Srpaulo MACSTR " result=%d - no query in progress", 218281806Srpaulo freq, MAC2STR(dst), result); 219281806Srpaulo return; 220281806Srpaulo } 221281806Srpaulo 222281806Srpaulo query = gas->current; 223281806Srpaulo 224281806Srpaulo dur = ms_from_time(&query->last_oper); 225281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR 226281806Srpaulo " result=%d query=%p dialog_token=%u dur=%d ms", 227281806Srpaulo freq, MAC2STR(dst), result, query, query->dialog_token, dur); 228281806Srpaulo if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) { 229281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); 230281806Srpaulo return; 231281806Srpaulo } 232281806Srpaulo os_get_reltime(&query->last_oper); 233281806Srpaulo 234281806Srpaulo if (result == OFFCHANNEL_SEND_ACTION_SUCCESS) { 235281806Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 236281806Srpaulo eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, 237281806Srpaulo gas_query_timeout, gas, query); 238281806Srpaulo } 239281806Srpaulo if (result == OFFCHANNEL_SEND_ACTION_FAILED) { 240281806Srpaulo eloop_cancel_timeout(gas_query_timeout, gas, query); 241281806Srpaulo eloop_register_timeout(0, 0, gas_query_timeout, gas, query); 242281806Srpaulo } 243281806Srpaulo} 244281806Srpaulo 245281806Srpaulo 246281806Srpaulostatic int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr) 247281806Srpaulo{ 248281806Srpaulo if (wpa_s->current_ssid == NULL || 249281806Srpaulo wpa_s->wpa_state < WPA_4WAY_HANDSHAKE || 250281806Srpaulo os_memcmp(addr, wpa_s->bssid, ETH_ALEN) != 0) 251281806Srpaulo return 0; 252281806Srpaulo return wpa_sm_pmf_enabled(wpa_s->wpa); 253281806Srpaulo} 254281806Srpaulo 255281806Srpaulo 256252190Srpaulostatic int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query, 257252190Srpaulo struct wpabuf *req) 258252190Srpaulo{ 259281806Srpaulo unsigned int wait_time; 260281806Srpaulo int res, prot = pmf_in_use(gas->wpa_s, query->addr); 261281806Srpaulo 262252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u " 263281806Srpaulo "freq=%d prot=%d", MAC2STR(query->addr), 264281806Srpaulo (unsigned int) wpabuf_len(req), query->freq, prot); 265281806Srpaulo if (prot) { 266281806Srpaulo u8 *categ = wpabuf_mhead_u8(req); 267281806Srpaulo *categ = WLAN_ACTION_PROTECTED_DUAL; 268281806Srpaulo } 269281806Srpaulo os_get_reltime(&query->last_oper); 270281806Srpaulo wait_time = 1000; 271281806Srpaulo if (gas->wpa_s->max_remain_on_chan && 272281806Srpaulo wait_time > gas->wpa_s->max_remain_on_chan) 273281806Srpaulo wait_time = gas->wpa_s->max_remain_on_chan; 274252190Srpaulo res = offchannel_send_action(gas->wpa_s, query->freq, query->addr, 275252190Srpaulo gas->wpa_s->own_addr, query->addr, 276281806Srpaulo wpabuf_head(req), wpabuf_len(req), 277281806Srpaulo wait_time, gas_query_tx_status, 0); 278252190Srpaulo if (res == 0) 279252190Srpaulo query->offchannel_tx_started = 1; 280252190Srpaulo return res; 281252190Srpaulo} 282252190Srpaulo 283252190Srpaulo 284252190Srpaulostatic void gas_query_tx_comeback_req(struct gas_query *gas, 285252190Srpaulo struct gas_query_pending *query) 286252190Srpaulo{ 287252190Srpaulo struct wpabuf *req; 288252190Srpaulo 289252190Srpaulo req = gas_build_comeback_req(query->dialog_token); 290252190Srpaulo if (req == NULL) { 291252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 292252190Srpaulo return; 293252190Srpaulo } 294252190Srpaulo 295252190Srpaulo if (gas_query_tx(gas, query, req) < 0) { 296252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " 297252190Srpaulo MACSTR, MAC2STR(query->addr)); 298252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 299252190Srpaulo } 300252190Srpaulo 301252190Srpaulo wpabuf_free(req); 302252190Srpaulo} 303252190Srpaulo 304252190Srpaulo 305252190Srpaulostatic void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx) 306252190Srpaulo{ 307252190Srpaulo struct gas_query *gas = eloop_data; 308252190Srpaulo struct gas_query_pending *query = user_ctx; 309252190Srpaulo 310252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR, 311252190Srpaulo MAC2STR(query->addr)); 312252190Srpaulo gas_query_tx_comeback_req(gas, query); 313252190Srpaulo} 314252190Srpaulo 315252190Srpaulo 316252190Srpaulostatic void gas_query_tx_comeback_req_delay(struct gas_query *gas, 317252190Srpaulo struct gas_query_pending *query, 318252190Srpaulo u16 comeback_delay) 319252190Srpaulo{ 320252190Srpaulo unsigned int secs, usecs; 321252190Srpaulo 322252190Srpaulo secs = (comeback_delay * 1024) / 1000000; 323252190Srpaulo usecs = comeback_delay * 1024 - secs * 1000000; 324252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR 325252190Srpaulo " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs); 326252190Srpaulo eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); 327252190Srpaulo eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout, 328252190Srpaulo gas, query); 329252190Srpaulo} 330252190Srpaulo 331252190Srpaulo 332252190Srpaulostatic void gas_query_rx_initial(struct gas_query *gas, 333252190Srpaulo struct gas_query_pending *query, 334252190Srpaulo const u8 *adv_proto, const u8 *resp, 335252190Srpaulo size_t len, u16 comeback_delay) 336252190Srpaulo{ 337252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Received initial response from " 338252190Srpaulo MACSTR " (dialog_token=%u comeback_delay=%u)", 339252190Srpaulo MAC2STR(query->addr), query->dialog_token, comeback_delay); 340252190Srpaulo 341252190Srpaulo query->adv_proto = wpabuf_alloc_copy(adv_proto, 2 + adv_proto[1]); 342252190Srpaulo if (query->adv_proto == NULL) { 343252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 344252190Srpaulo return; 345252190Srpaulo } 346252190Srpaulo 347252190Srpaulo if (comeback_delay) { 348252190Srpaulo query->wait_comeback = 1; 349252190Srpaulo gas_query_tx_comeback_req_delay(gas, query, comeback_delay); 350252190Srpaulo return; 351252190Srpaulo } 352252190Srpaulo 353252190Srpaulo /* Query was completed without comeback mechanism */ 354252190Srpaulo if (gas_query_append(query, resp, len) < 0) { 355252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 356252190Srpaulo return; 357252190Srpaulo } 358252190Srpaulo 359252190Srpaulo gas_query_done(gas, query, GAS_QUERY_SUCCESS); 360252190Srpaulo} 361252190Srpaulo 362252190Srpaulo 363252190Srpaulostatic void gas_query_rx_comeback(struct gas_query *gas, 364252190Srpaulo struct gas_query_pending *query, 365252190Srpaulo const u8 *adv_proto, const u8 *resp, 366252190Srpaulo size_t len, u8 frag_id, u8 more_frags, 367252190Srpaulo u16 comeback_delay) 368252190Srpaulo{ 369252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Received comeback response from " 370252190Srpaulo MACSTR " (dialog_token=%u frag_id=%u more_frags=%u " 371252190Srpaulo "comeback_delay=%u)", 372252190Srpaulo MAC2STR(query->addr), query->dialog_token, frag_id, 373252190Srpaulo more_frags, comeback_delay); 374252190Srpaulo 375252190Srpaulo if ((size_t) 2 + adv_proto[1] != wpabuf_len(query->adv_proto) || 376252190Srpaulo os_memcmp(adv_proto, wpabuf_head(query->adv_proto), 377252190Srpaulo wpabuf_len(query->adv_proto)) != 0) { 378252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed " 379252190Srpaulo "between initial and comeback response from " 380252190Srpaulo MACSTR, MAC2STR(query->addr)); 381252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 382252190Srpaulo return; 383252190Srpaulo } 384252190Srpaulo 385252190Srpaulo if (comeback_delay) { 386252190Srpaulo if (frag_id) { 387252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response " 388252190Srpaulo "with non-zero frag_id and comeback_delay " 389252190Srpaulo "from " MACSTR, MAC2STR(query->addr)); 390252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 391252190Srpaulo return; 392252190Srpaulo } 393252190Srpaulo gas_query_tx_comeback_req_delay(gas, query, comeback_delay); 394252190Srpaulo return; 395252190Srpaulo } 396252190Srpaulo 397252190Srpaulo if (frag_id != query->next_frag_id) { 398252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response " 399252190Srpaulo "from " MACSTR, MAC2STR(query->addr)); 400281806Srpaulo if (frag_id + 1 == query->next_frag_id) { 401281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible " 402281806Srpaulo "retry of previous fragment"); 403281806Srpaulo return; 404281806Srpaulo } 405252190Srpaulo gas_query_done(gas, query, GAS_QUERY_PEER_ERROR); 406252190Srpaulo return; 407252190Srpaulo } 408252190Srpaulo query->next_frag_id++; 409252190Srpaulo 410252190Srpaulo if (gas_query_append(query, resp, len) < 0) { 411252190Srpaulo gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR); 412252190Srpaulo return; 413252190Srpaulo } 414252190Srpaulo 415252190Srpaulo if (more_frags) { 416252190Srpaulo gas_query_tx_comeback_req(gas, query); 417252190Srpaulo return; 418252190Srpaulo } 419252190Srpaulo 420252190Srpaulo gas_query_done(gas, query, GAS_QUERY_SUCCESS); 421252190Srpaulo} 422252190Srpaulo 423252190Srpaulo 424252190Srpaulo/** 425281806Srpaulo * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame 426252190Srpaulo * @gas: GAS query data from gas_query_init() 427252190Srpaulo * @da: Destination MAC address of the Action frame 428252190Srpaulo * @sa: Source MAC address of the Action frame 429252190Srpaulo * @bssid: BSSID of the Action frame 430281806Srpaulo * @categ: Category of the Action frame 431252190Srpaulo * @data: Payload of the Action frame 432252190Srpaulo * @len: Length of @data 433252190Srpaulo * @freq: Frequency (in MHz) on which the frame was received 434252190Srpaulo * Returns: 0 if the Public Action frame was a GAS frame or -1 if not 435252190Srpaulo */ 436252190Srpauloint gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, 437281806Srpaulo const u8 *bssid, u8 categ, const u8 *data, size_t len, 438281806Srpaulo int freq) 439252190Srpaulo{ 440252190Srpaulo struct gas_query_pending *query; 441252190Srpaulo u8 action, dialog_token, frag_id = 0, more_frags = 0; 442252190Srpaulo u16 comeback_delay, resp_len; 443252190Srpaulo const u8 *pos, *adv_proto; 444281806Srpaulo int prot, pmf; 445281806Srpaulo unsigned int left; 446252190Srpaulo 447252190Srpaulo if (gas == NULL || len < 4) 448252190Srpaulo return -1; 449252190Srpaulo 450281806Srpaulo prot = categ == WLAN_ACTION_PROTECTED_DUAL; 451281806Srpaulo pmf = pmf_in_use(gas->wpa_s, bssid); 452281806Srpaulo if (prot && !pmf) { 453281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled"); 454281806Srpaulo return 0; 455281806Srpaulo } 456281806Srpaulo if (!prot && pmf) { 457281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled"); 458281806Srpaulo return 0; 459281806Srpaulo } 460281806Srpaulo 461252190Srpaulo pos = data; 462252190Srpaulo action = *pos++; 463252190Srpaulo dialog_token = *pos++; 464252190Srpaulo 465252190Srpaulo if (action != WLAN_PA_GAS_INITIAL_RESP && 466252190Srpaulo action != WLAN_PA_GAS_COMEBACK_RESP) 467252190Srpaulo return -1; /* Not a GAS response */ 468252190Srpaulo 469252190Srpaulo query = gas_query_get_pending(gas, sa, dialog_token); 470252190Srpaulo if (query == NULL) { 471252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR 472252190Srpaulo " dialog token %u", MAC2STR(sa), dialog_token); 473252190Srpaulo return -1; 474252190Srpaulo } 475252190Srpaulo 476281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR, 477281806Srpaulo ms_from_time(&query->last_oper), MAC2STR(sa)); 478281806Srpaulo 479252190Srpaulo if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) { 480252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from " 481252190Srpaulo MACSTR " dialog token %u when waiting for comeback " 482252190Srpaulo "response", MAC2STR(sa), dialog_token); 483252190Srpaulo return 0; 484252190Srpaulo } 485252190Srpaulo 486252190Srpaulo if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) { 487252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from " 488252190Srpaulo MACSTR " dialog token %u when waiting for initial " 489252190Srpaulo "response", MAC2STR(sa), dialog_token); 490252190Srpaulo return 0; 491252190Srpaulo } 492252190Srpaulo 493252190Srpaulo query->status_code = WPA_GET_LE16(pos); 494252190Srpaulo pos += 2; 495252190Srpaulo 496281806Srpaulo if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING && 497281806Srpaulo action == WLAN_PA_GAS_COMEBACK_RESP) { 498281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response"); 499281806Srpaulo } else if (query->status_code != WLAN_STATUS_SUCCESS) { 500252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token " 501252190Srpaulo "%u failed - status code %u", 502252190Srpaulo MAC2STR(sa), dialog_token, query->status_code); 503252190Srpaulo gas_query_done(gas, query, GAS_QUERY_FAILURE); 504252190Srpaulo return 0; 505252190Srpaulo } 506252190Srpaulo 507252190Srpaulo if (action == WLAN_PA_GAS_COMEBACK_RESP) { 508252190Srpaulo if (pos + 1 > data + len) 509252190Srpaulo return 0; 510252190Srpaulo frag_id = *pos & 0x7f; 511252190Srpaulo more_frags = (*pos & 0x80) >> 7; 512252190Srpaulo pos++; 513252190Srpaulo } 514252190Srpaulo 515252190Srpaulo /* Comeback Delay */ 516252190Srpaulo if (pos + 2 > data + len) 517252190Srpaulo return 0; 518252190Srpaulo comeback_delay = WPA_GET_LE16(pos); 519252190Srpaulo pos += 2; 520252190Srpaulo 521252190Srpaulo /* Advertisement Protocol element */ 522252190Srpaulo if (pos + 2 > data + len || pos + 2 + pos[1] > data + len) { 523252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement " 524252190Srpaulo "Protocol element in the response from " MACSTR, 525252190Srpaulo MAC2STR(sa)); 526252190Srpaulo return 0; 527252190Srpaulo } 528252190Srpaulo 529252190Srpaulo if (*pos != WLAN_EID_ADV_PROTO) { 530252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement " 531252190Srpaulo "Protocol element ID %u in response from " MACSTR, 532252190Srpaulo *pos, MAC2STR(sa)); 533252190Srpaulo return 0; 534252190Srpaulo } 535252190Srpaulo 536252190Srpaulo adv_proto = pos; 537252190Srpaulo pos += 2 + pos[1]; 538252190Srpaulo 539252190Srpaulo /* Query Response Length */ 540252190Srpaulo if (pos + 2 > data + len) { 541252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length"); 542252190Srpaulo return 0; 543252190Srpaulo } 544252190Srpaulo resp_len = WPA_GET_LE16(pos); 545252190Srpaulo pos += 2; 546252190Srpaulo 547281806Srpaulo left = data + len - pos; 548281806Srpaulo if (resp_len > left) { 549252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in " 550252190Srpaulo "response from " MACSTR, MAC2STR(sa)); 551252190Srpaulo return 0; 552252190Srpaulo } 553252190Srpaulo 554281806Srpaulo if (resp_len < left) { 555252190Srpaulo wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data " 556252190Srpaulo "after Query Response from " MACSTR, 557281806Srpaulo left - resp_len, MAC2STR(sa)); 558252190Srpaulo } 559252190Srpaulo 560252190Srpaulo if (action == WLAN_PA_GAS_COMEBACK_RESP) 561252190Srpaulo gas_query_rx_comeback(gas, query, adv_proto, pos, resp_len, 562252190Srpaulo frag_id, more_frags, comeback_delay); 563252190Srpaulo else 564252190Srpaulo gas_query_rx_initial(gas, query, adv_proto, pos, resp_len, 565252190Srpaulo comeback_delay); 566252190Srpaulo 567252190Srpaulo return 0; 568252190Srpaulo} 569252190Srpaulo 570252190Srpaulo 571252190Srpaulostatic void gas_query_timeout(void *eloop_data, void *user_ctx) 572252190Srpaulo{ 573252190Srpaulo struct gas_query *gas = eloop_data; 574252190Srpaulo struct gas_query_pending *query = user_ctx; 575252190Srpaulo 576281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR 577281806Srpaulo " dialog token %u", 578281806Srpaulo MAC2STR(query->addr), query->dialog_token); 579252190Srpaulo gas_query_done(gas, query, GAS_QUERY_TIMEOUT); 580252190Srpaulo} 581252190Srpaulo 582252190Srpaulo 583252190Srpaulostatic int gas_query_dialog_token_available(struct gas_query *gas, 584252190Srpaulo const u8 *dst, u8 dialog_token) 585252190Srpaulo{ 586252190Srpaulo struct gas_query_pending *q; 587252190Srpaulo dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { 588252190Srpaulo if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 && 589252190Srpaulo dialog_token == q->dialog_token) 590252190Srpaulo return 0; 591252190Srpaulo } 592252190Srpaulo 593252190Srpaulo return 1; 594252190Srpaulo} 595252190Srpaulo 596252190Srpaulo 597281806Srpaulostatic void gas_query_start_cb(struct wpa_radio_work *work, int deinit) 598281806Srpaulo{ 599281806Srpaulo struct gas_query_pending *query = work->ctx; 600281806Srpaulo struct gas_query *gas = query->gas; 601281806Srpaulo struct wpa_supplicant *wpa_s = gas->wpa_s; 602281806Srpaulo 603281806Srpaulo if (deinit) { 604281806Srpaulo if (work->started) { 605281806Srpaulo gas->work = NULL; 606281806Srpaulo gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); 607281806Srpaulo return; 608281806Srpaulo } 609281806Srpaulo 610281806Srpaulo gas_query_free(query, 1); 611281806Srpaulo return; 612281806Srpaulo } 613281806Srpaulo 614281806Srpaulo if (wpas_update_random_addr_disassoc(wpa_s) < 0) { 615281806Srpaulo wpa_msg(wpa_s, MSG_INFO, 616281806Srpaulo "Failed to assign random MAC address for GAS"); 617281806Srpaulo gas_query_free(query, 1); 618281806Srpaulo radio_work_done(work); 619281806Srpaulo return; 620281806Srpaulo } 621281806Srpaulo 622281806Srpaulo gas->work = work; 623281806Srpaulo 624281806Srpaulo if (gas_query_tx(gas, query, query->req) < 0) { 625281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " 626281806Srpaulo MACSTR, MAC2STR(query->addr)); 627281806Srpaulo gas_query_free(query, 1); 628281806Srpaulo return; 629281806Srpaulo } 630281806Srpaulo gas->current = query; 631281806Srpaulo 632281806Srpaulo wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u", 633281806Srpaulo query->dialog_token); 634281806Srpaulo eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, 635281806Srpaulo gas_query_timeout, gas, query); 636281806Srpaulo 637281806Srpaulo} 638281806Srpaulo 639281806Srpaulo 640252190Srpaulo/** 641252190Srpaulo * gas_query_req - Request a GAS query 642252190Srpaulo * @gas: GAS query data from gas_query_init() 643252190Srpaulo * @dst: Destination MAC address for the query 644252190Srpaulo * @freq: Frequency (in MHz) for the channel on which to send the query 645281806Srpaulo * @req: GAS query payload (to be freed by gas_query module in case of success 646281806Srpaulo * return) 647252190Srpaulo * @cb: Callback function for reporting GAS query result and response 648252190Srpaulo * @ctx: Context pointer to use with the @cb call 649252190Srpaulo * Returns: dialog token (>= 0) on success or -1 on failure 650252190Srpaulo */ 651252190Srpauloint gas_query_req(struct gas_query *gas, const u8 *dst, int freq, 652252190Srpaulo struct wpabuf *req, 653252190Srpaulo void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, 654252190Srpaulo enum gas_query_result result, 655252190Srpaulo const struct wpabuf *adv_proto, 656252190Srpaulo const struct wpabuf *resp, u16 status_code), 657252190Srpaulo void *ctx) 658252190Srpaulo{ 659252190Srpaulo struct gas_query_pending *query; 660252190Srpaulo int dialog_token; 661281806Srpaulo static int next_start = 0; 662252190Srpaulo 663252190Srpaulo if (wpabuf_len(req) < 3) 664252190Srpaulo return -1; 665252190Srpaulo 666252190Srpaulo for (dialog_token = 0; dialog_token < 256; dialog_token++) { 667281806Srpaulo if (gas_query_dialog_token_available( 668281806Srpaulo gas, dst, (next_start + dialog_token) % 256)) 669252190Srpaulo break; 670252190Srpaulo } 671252190Srpaulo if (dialog_token == 256) 672252190Srpaulo return -1; /* Too many pending queries */ 673281806Srpaulo dialog_token = (next_start + dialog_token) % 256; 674281806Srpaulo next_start = (dialog_token + 1) % 256; 675252190Srpaulo 676252190Srpaulo query = os_zalloc(sizeof(*query)); 677252190Srpaulo if (query == NULL) 678252190Srpaulo return -1; 679252190Srpaulo 680281806Srpaulo query->gas = gas; 681252190Srpaulo os_memcpy(query->addr, dst, ETH_ALEN); 682252190Srpaulo query->dialog_token = dialog_token; 683252190Srpaulo query->freq = freq; 684252190Srpaulo query->cb = cb; 685252190Srpaulo query->ctx = ctx; 686281806Srpaulo query->req = req; 687252190Srpaulo dl_list_add(&gas->pending, &query->list); 688252190Srpaulo 689252190Srpaulo *(wpabuf_mhead_u8(req) + 2) = dialog_token; 690252190Srpaulo 691281806Srpaulo wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_START "addr=" MACSTR 692281806Srpaulo " dialog_token=%u freq=%d", 693281806Srpaulo MAC2STR(query->addr), query->dialog_token, query->freq); 694281806Srpaulo 695281806Srpaulo if (radio_add_work(gas->wpa_s, freq, "gas-query", 0, gas_query_start_cb, 696281806Srpaulo query) < 0) { 697281806Srpaulo gas_query_free(query, 1); 698252190Srpaulo return -1; 699252190Srpaulo } 700252190Srpaulo 701252190Srpaulo return dialog_token; 702252190Srpaulo} 703252190Srpaulo 704252190Srpaulo 705252190Srpaulo/** 706252190Srpaulo * gas_query_cancel - Cancel a pending GAS query 707252190Srpaulo * @gas: GAS query data from gas_query_init() 708252190Srpaulo * @dst: Destination MAC address for the query 709252190Srpaulo * @dialog_token: Dialog token from gas_query_req() 710252190Srpaulo */ 711252190Srpaulovoid gas_query_cancel(struct gas_query *gas, const u8 *dst, u8 dialog_token) 712252190Srpaulo{ 713252190Srpaulo struct gas_query_pending *query; 714252190Srpaulo 715252190Srpaulo query = gas_query_get_pending(gas, dst, dialog_token); 716252190Srpaulo if (query) 717252190Srpaulo gas_query_done(gas, query, GAS_QUERY_CANCELLED); 718252190Srpaulo 719252190Srpaulo} 720