blob: c301f7410819f5eb9ab6c0f2ab255d5062299224 [file] [log] [blame]
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -08001/*
2 * Generic advertisement service (GAS) query
3 * Copyright (c) 2009, Atheros Communications
Dmitry Shmidt18463232014-01-24 12:29:41 -08004 * Copyright (c) 2011-2014, Qualcomm Atheros, Inc.
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -08005 * Copyright (c) 2011-2014, Jouni Malinen <j@w1.fi>
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -08006 *
Dmitry Shmidtc5ec7f52012-03-06 16:33:24 -08007 * This software may be distributed under the terms of the BSD license.
8 * See README for more details.
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -08009 */
10
11#include "includes.h"
12
13#include "common.h"
14#include "utils/eloop.h"
15#include "common/ieee802_11_defs.h"
16#include "common/gas.h"
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -080017#include "common/wpa_ctrl.h"
Dmitry Shmidt18463232014-01-24 12:29:41 -080018#include "rsn_supp/wpa.h"
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080019#include "wpa_supplicant_i.h"
Dmitry Shmidtd5ab1b52016-06-21 12:38:41 -070020#include "config.h"
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080021#include "driver_i.h"
22#include "offchannel.h"
23#include "gas_query.h"
24
25
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -080026/** GAS query timeout in seconds */
Dmitry Shmidtb6e9aaf2013-05-20 14:49:44 -070027#define GAS_QUERY_TIMEOUT_PERIOD 2
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080028
Dmitry Shmidt7d56b752015-12-22 10:59:44 -080029/* GAS query wait-time / duration in ms */
30#define GAS_QUERY_WAIT_TIME_INITIAL 1000
31#define GAS_QUERY_WAIT_TIME_COMEBACK 150
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080032
Sunil8cd6f4d2022-06-28 18:40:46 +000033#define GAS_QUERY_MAX_COMEBACK_DELAY 60000
34
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -080035/**
36 * struct gas_query_pending - Pending GAS query
37 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080038struct gas_query_pending {
39 struct dl_list list;
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -080040 struct gas_query *gas;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080041 u8 addr[ETH_ALEN];
42 u8 dialog_token;
43 u8 next_frag_id;
44 unsigned int wait_comeback:1;
45 unsigned int offchannel_tx_started:1;
Dmitry Shmidt7d56b752015-12-22 10:59:44 -080046 unsigned int retry:1;
Roshan Pius3a1667e2018-07-03 15:17:14 -070047 unsigned int wildcard_bssid:1;
Hai Shalomb755a2a2020-04-23 21:49:02 -070048 unsigned int maintain_addr:1;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080049 int freq;
50 u16 status_code;
Dmitry Shmidt051af732013-10-22 13:52:46 -070051 struct wpabuf *req;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080052 struct wpabuf *adv_proto;
53 struct wpabuf *resp;
Dmitry Shmidtf21452a2014-02-26 10:55:25 -080054 struct os_reltime last_oper;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080055 void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
56 enum gas_query_result result,
57 const struct wpabuf *adv_proto,
58 const struct wpabuf *resp, u16 status_code);
59 void *ctx;
Dmitry Shmidtebd93af2017-02-21 13:40:44 -080060 u8 sa[ETH_ALEN];
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080061};
62
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -080063/**
64 * struct gas_query - Internal GAS query data
65 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080066struct gas_query {
67 struct wpa_supplicant *wpa_s;
68 struct dl_list pending; /* struct gas_query_pending */
Dmitry Shmidt051af732013-10-22 13:52:46 -070069 struct gas_query_pending *current;
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -080070 struct wpa_radio_work *work;
Dmitry Shmidtebd93af2017-02-21 13:40:44 -080071 struct os_reltime last_mac_addr_rand;
72 int last_rand_sa_type;
73 u8 rand_addr[ETH_ALEN];
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080074};
75
76
77static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx);
78static void gas_query_timeout(void *eloop_data, void *user_ctx);
Dmitry Shmidt7d56b752015-12-22 10:59:44 -080079static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx);
80static void gas_query_tx_initial_req(struct gas_query *gas,
81 struct gas_query_pending *query);
82static int gas_query_new_dialog_token(struct gas_query *gas, const u8 *dst);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080083
84
Dmitry Shmidtf21452a2014-02-26 10:55:25 -080085static int ms_from_time(struct os_reltime *last)
86{
87 struct os_reltime now, res;
88
89 os_get_reltime(&now);
90 os_reltime_sub(&now, last, &res);
91 return res.sec * 1000 + res.usec / 1000;
92}
93
94
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -080095/**
96 * gas_query_init - Initialize GAS query component
97 * @wpa_s: Pointer to wpa_supplicant data
98 * Returns: Pointer to GAS query data or %NULL on failure
99 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800100struct gas_query * gas_query_init(struct wpa_supplicant *wpa_s)
101{
102 struct gas_query *gas;
103
104 gas = os_zalloc(sizeof(*gas));
105 if (gas == NULL)
106 return NULL;
107
108 gas->wpa_s = wpa_s;
109 dl_list_init(&gas->pending);
110
111 return gas;
112}
113
114
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800115static const char * gas_result_txt(enum gas_query_result result)
116{
117 switch (result) {
118 case GAS_QUERY_SUCCESS:
119 return "SUCCESS";
120 case GAS_QUERY_FAILURE:
121 return "FAILURE";
122 case GAS_QUERY_TIMEOUT:
123 return "TIMEOUT";
124 case GAS_QUERY_PEER_ERROR:
125 return "PEER_ERROR";
126 case GAS_QUERY_INTERNAL_ERROR:
127 return "INTERNAL_ERROR";
Roshan Pius3a1667e2018-07-03 15:17:14 -0700128 case GAS_QUERY_STOPPED:
129 return "STOPPED";
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800130 case GAS_QUERY_DELETED_AT_DEINIT:
131 return "DELETED_AT_DEINIT";
132 }
133
134 return "N/A";
135}
136
137
138static void gas_query_free(struct gas_query_pending *query, int del_list)
139{
140 struct gas_query *gas = query->gas;
141
142 if (del_list)
143 dl_list_del(&query->list);
144
145 if (gas->work && gas->work->ctx == query) {
146 radio_work_done(gas->work);
147 gas->work = NULL;
148 }
149
150 wpabuf_free(query->req);
151 wpabuf_free(query->adv_proto);
152 wpabuf_free(query->resp);
153 os_free(query);
154}
155
156
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800157static void gas_query_done(struct gas_query *gas,
158 struct gas_query_pending *query,
159 enum gas_query_result result)
160{
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800161 wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR
162 " dialog_token=%u freq=%d status_code=%u result=%s",
163 MAC2STR(query->addr), query->dialog_token, query->freq,
164 query->status_code, gas_result_txt(result));
Dmitry Shmidt051af732013-10-22 13:52:46 -0700165 if (gas->current == query)
166 gas->current = NULL;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800167 if (query->offchannel_tx_started)
168 offchannel_send_action_done(gas->wpa_s);
169 eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
170 eloop_cancel_timeout(gas_query_timeout, gas, query);
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800171 eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800172 dl_list_del(&query->list);
173 query->cb(query->ctx, query->addr, query->dialog_token, result,
174 query->adv_proto, query->resp, query->status_code);
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800175 gas_query_free(query, 0);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800176}
177
178
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800179/**
180 * gas_query_deinit - Deinitialize GAS query component
181 * @gas: GAS query data from gas_query_init()
182 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800183void gas_query_deinit(struct gas_query *gas)
184{
185 struct gas_query_pending *query, *next;
186
187 if (gas == NULL)
188 return;
189
190 dl_list_for_each_safe(query, next, &gas->pending,
191 struct gas_query_pending, list)
192 gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT);
193
194 os_free(gas);
195}
196
197
198static struct gas_query_pending *
199gas_query_get_pending(struct gas_query *gas, const u8 *addr, u8 dialog_token)
200{
201 struct gas_query_pending *q;
202 dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
203 if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
204 q->dialog_token == dialog_token)
205 return q;
206 }
207 return NULL;
208}
209
210
211static int gas_query_append(struct gas_query_pending *query, const u8 *data,
212 size_t len)
213{
214 if (wpabuf_resize(&query->resp, len) < 0) {
215 wpa_printf(MSG_DEBUG, "GAS: No memory to store the response");
216 return -1;
217 }
218 wpabuf_put_data(query->resp, data, len);
219 return 0;
220}
221
222
Dmitry Shmidt051af732013-10-22 13:52:46 -0700223static void gas_query_tx_status(struct wpa_supplicant *wpa_s,
224 unsigned int freq, const u8 *dst,
225 const u8 *src, const u8 *bssid,
226 const u8 *data, size_t data_len,
227 enum offchannel_send_action_result result)
228{
229 struct gas_query_pending *query;
230 struct gas_query *gas = wpa_s->gas;
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800231 int dur;
Dmitry Shmidt051af732013-10-22 13:52:46 -0700232
233 if (gas->current == NULL) {
234 wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst="
235 MACSTR " result=%d - no query in progress",
236 freq, MAC2STR(dst), result);
237 return;
238 }
239
240 query = gas->current;
241
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800242 dur = ms_from_time(&query->last_oper);
Dmitry Shmidt051af732013-10-22 13:52:46 -0700243 wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800244 " result=%d query=%p dialog_token=%u dur=%d ms",
245 freq, MAC2STR(dst), result, query, query->dialog_token, dur);
Dmitry Shmidt051af732013-10-22 13:52:46 -0700246 if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) {
247 wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination");
248 return;
249 }
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800250 os_get_reltime(&query->last_oper);
Dmitry Shmidt051af732013-10-22 13:52:46 -0700251
Dmitry Shmidtd2986c22017-10-23 14:22:09 -0700252 if (result == OFFCHANNEL_SEND_ACTION_SUCCESS ||
253 result == OFFCHANNEL_SEND_ACTION_NO_ACK) {
Dmitry Shmidt051af732013-10-22 13:52:46 -0700254 eloop_cancel_timeout(gas_query_timeout, gas, query);
Dmitry Shmidtd2986c22017-10-23 14:22:09 -0700255 if (result == OFFCHANNEL_SEND_ACTION_NO_ACK) {
256 wpa_printf(MSG_DEBUG, "GAS: No ACK to GAS request");
257 eloop_register_timeout(0, 250000,
258 gas_query_timeout, gas, query);
259 } else {
260 eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0,
261 gas_query_timeout, gas, query);
262 }
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800263 if (query->wait_comeback && !query->retry) {
264 eloop_cancel_timeout(gas_query_rx_comeback_timeout,
265 gas, query);
266 eloop_register_timeout(
267 0, (GAS_QUERY_WAIT_TIME_COMEBACK + 10) * 1000,
268 gas_query_rx_comeback_timeout, gas, query);
269 }
Dmitry Shmidt051af732013-10-22 13:52:46 -0700270 }
271 if (result == OFFCHANNEL_SEND_ACTION_FAILED) {
272 eloop_cancel_timeout(gas_query_timeout, gas, query);
273 eloop_register_timeout(0, 0, gas_query_timeout, gas, query);
274 }
275}
276
277
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800278static int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query,
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800279 struct wpabuf *req, unsigned int wait_time)
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800280{
Dmitry Shmidt18463232014-01-24 12:29:41 -0800281 int res, prot = pmf_in_use(gas->wpa_s, query->addr);
Dmitry Shmidtd5ab1b52016-06-21 12:38:41 -0700282 const u8 *bssid;
283 const u8 wildcard_bssid[ETH_ALEN] = {
284 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
285 };
Dmitry Shmidt18463232014-01-24 12:29:41 -0800286
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800287 wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u "
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800288 "freq=%d prot=%d using src addr " MACSTR,
289 MAC2STR(query->addr), (unsigned int) wpabuf_len(req),
290 query->freq, prot, MAC2STR(query->sa));
Dmitry Shmidt18463232014-01-24 12:29:41 -0800291 if (prot) {
292 u8 *categ = wpabuf_mhead_u8(req);
293 *categ = WLAN_ACTION_PROTECTED_DUAL;
294 }
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800295 os_get_reltime(&query->last_oper);
Dmitry Shmidt623d63a2014-06-13 11:05:14 -0700296 if (gas->wpa_s->max_remain_on_chan &&
297 wait_time > gas->wpa_s->max_remain_on_chan)
298 wait_time = gas->wpa_s->max_remain_on_chan;
Roshan Pius3a1667e2018-07-03 15:17:14 -0700299 if (!query->wildcard_bssid &&
300 (!gas->wpa_s->conf->gas_address3 ||
301 (gas->wpa_s->current_ssid &&
302 gas->wpa_s->wpa_state >= WPA_ASSOCIATED &&
303 os_memcmp(query->addr, gas->wpa_s->bssid, ETH_ALEN) == 0)))
Dmitry Shmidtd5ab1b52016-06-21 12:38:41 -0700304 bssid = query->addr;
305 else
306 bssid = wildcard_bssid;
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800307
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800308 res = offchannel_send_action(gas->wpa_s, query->freq, query->addr,
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800309 query->sa, bssid, wpabuf_head(req),
310 wpabuf_len(req), wait_time,
311 gas_query_tx_status, 0);
312
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800313 if (res == 0)
314 query->offchannel_tx_started = 1;
315 return res;
316}
317
318
319static void gas_query_tx_comeback_req(struct gas_query *gas,
320 struct gas_query_pending *query)
321{
322 struct wpabuf *req;
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800323 unsigned int wait_time;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800324
325 req = gas_build_comeback_req(query->dialog_token);
326 if (req == NULL) {
327 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
328 return;
329 }
330
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800331 wait_time = (query->retry || !query->offchannel_tx_started) ?
332 GAS_QUERY_WAIT_TIME_INITIAL : GAS_QUERY_WAIT_TIME_COMEBACK;
333
334 if (gas_query_tx(gas, query, req, wait_time) < 0) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800335 wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
336 MACSTR, MAC2STR(query->addr));
337 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
338 }
339
340 wpabuf_free(req);
341}
342
343
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800344static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx)
345{
346 struct gas_query *gas = eloop_data;
347 struct gas_query_pending *query = user_ctx;
348 int dialog_token;
349
350 wpa_printf(MSG_DEBUG,
351 "GAS: No response to comeback request received (retry=%u)",
352 query->retry);
353 if (gas->current != query || query->retry)
354 return;
355 dialog_token = gas_query_new_dialog_token(gas, query->addr);
356 if (dialog_token < 0)
357 return;
358 wpa_printf(MSG_DEBUG,
359 "GAS: Retry GAS query due to comeback response timeout");
360 query->retry = 1;
361 query->dialog_token = dialog_token;
362 *(wpabuf_mhead_u8(query->req) + 2) = dialog_token;
363 query->wait_comeback = 0;
364 query->next_frag_id = 0;
365 wpabuf_free(query->adv_proto);
366 query->adv_proto = NULL;
367 eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
368 eloop_cancel_timeout(gas_query_timeout, gas, query);
369 gas_query_tx_initial_req(gas, query);
370}
371
372
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800373static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx)
374{
375 struct gas_query *gas = eloop_data;
376 struct gas_query_pending *query = user_ctx;
377
378 wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR,
379 MAC2STR(query->addr));
380 gas_query_tx_comeback_req(gas, query);
381}
382
383
384static void gas_query_tx_comeback_req_delay(struct gas_query *gas,
385 struct gas_query_pending *query,
386 u16 comeback_delay)
387{
388 unsigned int secs, usecs;
389
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800390 if (comeback_delay > 1 && query->offchannel_tx_started) {
391 offchannel_send_action_done(gas->wpa_s);
392 query->offchannel_tx_started = 0;
393 }
394
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800395 secs = (comeback_delay * 1024) / 1000000;
396 usecs = comeback_delay * 1024 - secs * 1000000;
397 wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR
398 " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs);
399 eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
400 eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout,
401 gas, query);
402}
403
404
405static void gas_query_rx_initial(struct gas_query *gas,
406 struct gas_query_pending *query,
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000407 const u8 *adv_proto, size_t adv_proto_len,
408 const u8 *resp, size_t len, u16 comeback_delay)
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800409{
410 wpa_printf(MSG_DEBUG, "GAS: Received initial response from "
411 MACSTR " (dialog_token=%u comeback_delay=%u)",
412 MAC2STR(query->addr), query->dialog_token, comeback_delay);
413
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000414 query->adv_proto = wpabuf_alloc_copy(adv_proto, adv_proto_len);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800415 if (query->adv_proto == NULL) {
416 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
417 return;
418 }
419
420 if (comeback_delay) {
Paul Stewart092955c2017-02-06 09:13:09 -0800421 eloop_cancel_timeout(gas_query_timeout, gas, query);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800422 query->wait_comeback = 1;
423 gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
424 return;
425 }
426
427 /* Query was completed without comeback mechanism */
428 if (gas_query_append(query, resp, len) < 0) {
429 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
430 return;
431 }
432
433 gas_query_done(gas, query, GAS_QUERY_SUCCESS);
434}
435
436
437static void gas_query_rx_comeback(struct gas_query *gas,
438 struct gas_query_pending *query,
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000439 const u8 *adv_proto, size_t adv_proto_len,
440 const u8 *resp, size_t len, u8 frag_id,
441 u8 more_frags, u16 comeback_delay)
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800442{
443 wpa_printf(MSG_DEBUG, "GAS: Received comeback response from "
444 MACSTR " (dialog_token=%u frag_id=%u more_frags=%u "
445 "comeback_delay=%u)",
446 MAC2STR(query->addr), query->dialog_token, frag_id,
447 more_frags, comeback_delay);
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800448 eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800449
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000450 if (adv_proto_len != wpabuf_len(query->adv_proto) ||
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800451 os_memcmp(adv_proto, wpabuf_head(query->adv_proto),
452 wpabuf_len(query->adv_proto)) != 0) {
453 wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed "
454 "between initial and comeback response from "
455 MACSTR, MAC2STR(query->addr));
456 gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
457 return;
458 }
459
460 if (comeback_delay) {
461 if (frag_id) {
462 wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response "
463 "with non-zero frag_id and comeback_delay "
464 "from " MACSTR, MAC2STR(query->addr));
465 gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
466 return;
467 }
468 gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
469 return;
470 }
471
472 if (frag_id != query->next_frag_id) {
473 wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response "
474 "from " MACSTR, MAC2STR(query->addr));
Dmitry Shmidtb6e9aaf2013-05-20 14:49:44 -0700475 if (frag_id + 1 == query->next_frag_id) {
476 wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible "
477 "retry of previous fragment");
478 return;
479 }
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800480 gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
481 return;
482 }
483 query->next_frag_id++;
484
485 if (gas_query_append(query, resp, len) < 0) {
486 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
487 return;
488 }
489
490 if (more_frags) {
491 gas_query_tx_comeback_req(gas, query);
492 return;
493 }
494
495 gas_query_done(gas, query, GAS_QUERY_SUCCESS);
496}
497
498
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800499/**
Dmitry Shmidt18463232014-01-24 12:29:41 -0800500 * gas_query_rx - Indicate reception of a Public Action or Protected Dual frame
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800501 * @gas: GAS query data from gas_query_init()
502 * @da: Destination MAC address of the Action frame
503 * @sa: Source MAC address of the Action frame
504 * @bssid: BSSID of the Action frame
Dmitry Shmidt18463232014-01-24 12:29:41 -0800505 * @categ: Category of the Action frame
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800506 * @data: Payload of the Action frame
507 * @len: Length of @data
508 * @freq: Frequency (in MHz) on which the frame was received
509 * Returns: 0 if the Public Action frame was a GAS frame or -1 if not
510 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800511int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa,
Dmitry Shmidt18463232014-01-24 12:29:41 -0800512 const u8 *bssid, u8 categ, const u8 *data, size_t len,
513 int freq)
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800514{
515 struct gas_query_pending *query;
516 u8 action, dialog_token, frag_id = 0, more_frags = 0;
517 u16 comeback_delay, resp_len;
518 const u8 *pos, *adv_proto;
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000519 size_t adv_proto_len;
Dmitry Shmidt18463232014-01-24 12:29:41 -0800520 int prot, pmf;
Dmitry Shmidtfb45fd52015-01-05 13:08:17 -0800521 unsigned int left;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800522
523 if (gas == NULL || len < 4)
524 return -1;
525
Dmitry Shmidt4ae50e62016-06-27 13:48:39 -0700526 pos = data;
527 action = *pos++;
528 dialog_token = *pos++;
529
530 if (action != WLAN_PA_GAS_INITIAL_RESP &&
531 action != WLAN_PA_GAS_COMEBACK_RESP)
532 return -1; /* Not a GAS response */
533
Dmitry Shmidt18463232014-01-24 12:29:41 -0800534 prot = categ == WLAN_ACTION_PROTECTED_DUAL;
Dmitry Shmidt9c175262016-03-03 10:20:07 -0800535 pmf = pmf_in_use(gas->wpa_s, sa);
Dmitry Shmidt18463232014-01-24 12:29:41 -0800536 if (prot && !pmf) {
537 wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled");
538 return 0;
539 }
540 if (!prot && pmf) {
541 wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled");
542 return 0;
543 }
544
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800545 query = gas_query_get_pending(gas, sa, dialog_token);
546 if (query == NULL) {
547 wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR
548 " dialog token %u", MAC2STR(sa), dialog_token);
549 return -1;
550 }
551
Dmitry Shmidtf21452a2014-02-26 10:55:25 -0800552 wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR,
553 ms_from_time(&query->last_oper), MAC2STR(sa));
554
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800555 if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) {
556 wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from "
557 MACSTR " dialog token %u when waiting for comeback "
558 "response", MAC2STR(sa), dialog_token);
559 return 0;
560 }
561
562 if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) {
563 wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from "
564 MACSTR " dialog token %u when waiting for initial "
565 "response", MAC2STR(sa), dialog_token);
566 return 0;
567 }
568
569 query->status_code = WPA_GET_LE16(pos);
570 pos += 2;
571
Dmitry Shmidt7d5c8f22014-03-03 13:53:28 -0800572 if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING &&
573 action == WLAN_PA_GAS_COMEBACK_RESP) {
574 wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response");
575 } else if (query->status_code != WLAN_STATUS_SUCCESS) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800576 wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token "
577 "%u failed - status code %u",
578 MAC2STR(sa), dialog_token, query->status_code);
579 gas_query_done(gas, query, GAS_QUERY_FAILURE);
580 return 0;
581 }
582
583 if (action == WLAN_PA_GAS_COMEBACK_RESP) {
584 if (pos + 1 > data + len)
585 return 0;
586 frag_id = *pos & 0x7f;
587 more_frags = (*pos & 0x80) >> 7;
588 pos++;
589 }
590
591 /* Comeback Delay */
592 if (pos + 2 > data + len)
593 return 0;
594 comeback_delay = WPA_GET_LE16(pos);
Sunil8cd6f4d2022-06-28 18:40:46 +0000595 if (comeback_delay > GAS_QUERY_MAX_COMEBACK_DELAY)
596 comeback_delay = GAS_QUERY_MAX_COMEBACK_DELAY;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800597 pos += 2;
598
599 /* Advertisement Protocol element */
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000600 adv_proto = pos;
601 left = data + len - adv_proto;
602 if (left < 2 || adv_proto[1] > left - 2) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800603 wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement "
604 "Protocol element in the response from " MACSTR,
605 MAC2STR(sa));
606 return 0;
607 }
608
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000609 if (*adv_proto != WLAN_EID_ADV_PROTO) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800610 wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement "
611 "Protocol element ID %u in response from " MACSTR,
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000612 *adv_proto, MAC2STR(sa));
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800613 return 0;
614 }
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000615 adv_proto_len = 2 + adv_proto[1];
616 if (adv_proto_len > (size_t) (data + len - pos))
617 return 0; /* unreachable due to an earlier check */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800618
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000619 pos += adv_proto_len;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800620
621 /* Query Response Length */
622 if (pos + 2 > data + len) {
623 wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length");
624 return 0;
625 }
626 resp_len = WPA_GET_LE16(pos);
627 pos += 2;
628
Dmitry Shmidtfb45fd52015-01-05 13:08:17 -0800629 left = data + len - pos;
630 if (resp_len > left) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800631 wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in "
632 "response from " MACSTR, MAC2STR(sa));
633 return 0;
634 }
635
Dmitry Shmidtfb45fd52015-01-05 13:08:17 -0800636 if (resp_len < left) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800637 wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data "
638 "after Query Response from " MACSTR,
Dmitry Shmidtfb45fd52015-01-05 13:08:17 -0800639 left - resp_len, MAC2STR(sa));
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800640 }
641
642 if (action == WLAN_PA_GAS_COMEBACK_RESP)
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000643 gas_query_rx_comeback(gas, query, adv_proto, adv_proto_len,
644 pos, resp_len, frag_id, more_frags,
645 comeback_delay);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800646 else
Sunil Ravi38ad1ed2023-01-17 23:58:31 +0000647 gas_query_rx_initial(gas, query, adv_proto, adv_proto_len,
648 pos, resp_len, comeback_delay);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800649
650 return 0;
651}
652
653
654static void gas_query_timeout(void *eloop_data, void *user_ctx)
655{
656 struct gas_query *gas = eloop_data;
657 struct gas_query_pending *query = user_ctx;
658
Dmitry Shmidt051af732013-10-22 13:52:46 -0700659 wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR
660 " dialog token %u",
661 MAC2STR(query->addr), query->dialog_token);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800662 gas_query_done(gas, query, GAS_QUERY_TIMEOUT);
663}
664
665
666static int gas_query_dialog_token_available(struct gas_query *gas,
667 const u8 *dst, u8 dialog_token)
668{
669 struct gas_query_pending *q;
670 dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
671 if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
672 dialog_token == q->dialog_token)
673 return 0;
674 }
675
676 return 1;
677}
678
679
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800680static void gas_query_start_cb(struct wpa_radio_work *work, int deinit)
681{
682 struct gas_query_pending *query = work->ctx;
683 struct gas_query *gas = query->gas;
Dmitry Shmidt661b4f72014-09-29 14:58:27 -0700684 struct wpa_supplicant *wpa_s = gas->wpa_s;
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800685
686 if (deinit) {
Dmitry Shmidtbd14a572014-02-18 10:33:49 -0800687 if (work->started) {
688 gas->work = NULL;
689 gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT);
690 return;
691 }
692
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800693 gas_query_free(query, 1);
694 return;
695 }
696
Hai Shalom899fcc72020-10-19 14:38:18 -0700697 if (!query->maintain_addr && !wpa_s->conf->gas_rand_mac_addr) {
698 if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
699 wpa_msg(wpa_s, MSG_INFO,
700 "Failed to assign random MAC address for GAS");
701 gas_query_free(query, 1);
702 radio_work_done(work);
703 return;
704 }
705 os_memcpy(query->sa, wpa_s->own_addr, ETH_ALEN);
Dmitry Shmidt661b4f72014-09-29 14:58:27 -0700706 }
707
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800708 gas->work = work;
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800709 gas_query_tx_initial_req(gas, query);
710}
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800711
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800712
713static void gas_query_tx_initial_req(struct gas_query *gas,
714 struct gas_query_pending *query)
715{
716 if (gas_query_tx(gas, query, query->req,
717 GAS_QUERY_WAIT_TIME_INITIAL) < 0) {
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800718 wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
719 MACSTR, MAC2STR(query->addr));
Dmitry Shmidt4ae50e62016-06-27 13:48:39 -0700720 gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800721 return;
722 }
723 gas->current = query;
724
725 wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u",
726 query->dialog_token);
727 eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0,
728 gas_query_timeout, gas, query);
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800729}
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800730
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800731
732static int gas_query_new_dialog_token(struct gas_query *gas, const u8 *dst)
733{
Hai Shalome21d4e82020-04-29 16:34:06 -0700734 u8 dialog_token;
735 int i;
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800736
Hai Shalome21d4e82020-04-29 16:34:06 -0700737 /* There should never be more than couple active GAS queries in
738 * progress, so it should be very likely to find an available dialog
739 * token by checking random values. Use a limit on the number of
740 * iterations to handle the unexpected case of large number of pending
741 * queries cleanly. */
742 for (i = 0; i < 256; i++) {
743 /* Get a random number and check if the slot is available */
744 if (os_get_random(&dialog_token, sizeof(dialog_token)) < 0)
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800745 break;
Hai Shalome21d4e82020-04-29 16:34:06 -0700746 if (gas_query_dialog_token_available(gas, dst, dialog_token))
747 return dialog_token;
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800748 }
Hai Shalome21d4e82020-04-29 16:34:06 -0700749
750 /* No dialog token value available */
751 return -1;
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800752}
753
754
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800755static int gas_query_set_sa(struct gas_query *gas,
756 struct gas_query_pending *query)
757{
758 struct wpa_supplicant *wpa_s = gas->wpa_s;
759 struct os_reltime now;
760
Hai Shalomb755a2a2020-04-23 21:49:02 -0700761 if (query->maintain_addr ||
762 !wpa_s->conf->gas_rand_mac_addr ||
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800763 !(wpa_s->current_bss ?
764 (wpa_s->drv_flags &
765 WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA_CONNECTED) :
766 (wpa_s->drv_flags & WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA))) {
767 /* Use own MAC address as the transmitter address */
Hai Shalomb755a2a2020-04-23 21:49:02 -0700768 wpa_printf(MSG_DEBUG,
769 "GAS: Use own MAC address as the transmitter address%s%s%s",
770 query->maintain_addr ? " (maintain_addr)" : "",
771 !wpa_s->conf->gas_rand_mac_addr ? " (no gas_rand_mac_adr set)" : "",
772 !(wpa_s->current_bss ?
773 (wpa_s->drv_flags &
774 WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA_CONNECTED) :
775 (wpa_s->drv_flags &
776 WPA_DRIVER_FLAGS_MGMT_TX_RANDOM_TA)) ?
777 " (no driver rand capa" : "");
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800778 os_memcpy(query->sa, wpa_s->own_addr, ETH_ALEN);
779 return 0;
780 }
781
782 os_get_reltime(&now);
783
784 if (wpa_s->conf->gas_rand_mac_addr == gas->last_rand_sa_type &&
785 gas->last_mac_addr_rand.sec != 0 &&
786 !os_reltime_expired(&now, &gas->last_mac_addr_rand,
787 wpa_s->conf->gas_rand_addr_lifetime)) {
788 wpa_printf(MSG_DEBUG,
789 "GAS: Use the previously selected random transmitter address "
790 MACSTR, MAC2STR(gas->rand_addr));
791 os_memcpy(query->sa, gas->rand_addr, ETH_ALEN);
792 return 0;
793 }
794
795 if (wpa_s->conf->gas_rand_mac_addr == 1 &&
796 random_mac_addr(gas->rand_addr) < 0) {
797 wpa_printf(MSG_ERROR, "GAS: Failed to get random address");
798 return -1;
799 }
800
801 if (wpa_s->conf->gas_rand_mac_addr == 2 &&
802 random_mac_addr_keep_oui(gas->rand_addr) < 0) {
803 wpa_printf(MSG_ERROR,
804 "GAS: Failed to get random address with same OUI");
805 return -1;
806 }
807
808 wpa_printf(MSG_DEBUG, "GAS: Use a new random transmitter address "
809 MACSTR, MAC2STR(gas->rand_addr));
810 os_memcpy(query->sa, gas->rand_addr, ETH_ALEN);
811 os_get_reltime(&gas->last_mac_addr_rand);
812 gas->last_rand_sa_type = wpa_s->conf->gas_rand_mac_addr;
813
814 return 0;
815}
816
817
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800818/**
819 * gas_query_req - Request a GAS query
820 * @gas: GAS query data from gas_query_init()
821 * @dst: Destination MAC address for the query
822 * @freq: Frequency (in MHz) for the channel on which to send the query
Hai Shalomb755a2a2020-04-23 21:49:02 -0700823 * @wildcard_bssid: Force use of wildcard BSSID value
824 * @maintain_addr: Maintain own MAC address for exchange (i.e., ignore MAC
825 * address randomization rules)
Dmitry Shmidt051af732013-10-22 13:52:46 -0700826 * @req: GAS query payload (to be freed by gas_query module in case of success
827 * return)
Dmitry Shmidta54fa5f2013-01-15 13:53:35 -0800828 * @cb: Callback function for reporting GAS query result and response
829 * @ctx: Context pointer to use with the @cb call
830 * Returns: dialog token (>= 0) on success or -1 on failure
831 */
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800832int gas_query_req(struct gas_query *gas, const u8 *dst, int freq,
Hai Shalomb755a2a2020-04-23 21:49:02 -0700833 int wildcard_bssid, int maintain_addr, struct wpabuf *req,
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800834 void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
835 enum gas_query_result result,
836 const struct wpabuf *adv_proto,
837 const struct wpabuf *resp, u16 status_code),
838 void *ctx)
839{
840 struct gas_query_pending *query;
841 int dialog_token;
842
843 if (wpabuf_len(req) < 3)
844 return -1;
845
Dmitry Shmidt7d56b752015-12-22 10:59:44 -0800846 dialog_token = gas_query_new_dialog_token(gas, dst);
847 if (dialog_token < 0)
848 return -1;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800849
850 query = os_zalloc(sizeof(*query));
851 if (query == NULL)
852 return -1;
853
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800854 query->gas = gas;
Hai Shalomb755a2a2020-04-23 21:49:02 -0700855 query->maintain_addr = !!maintain_addr;
Dmitry Shmidtebd93af2017-02-21 13:40:44 -0800856 if (gas_query_set_sa(gas, query)) {
857 os_free(query);
858 return -1;
859 }
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800860 os_memcpy(query->addr, dst, ETH_ALEN);
861 query->dialog_token = dialog_token;
Roshan Pius3a1667e2018-07-03 15:17:14 -0700862 query->wildcard_bssid = !!wildcard_bssid;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800863 query->freq = freq;
864 query->cb = cb;
865 query->ctx = ctx;
Dmitry Shmidt051af732013-10-22 13:52:46 -0700866 query->req = req;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800867 dl_list_add(&gas->pending, &query->list);
868
869 *(wpabuf_mhead_u8(req) + 2) = dialog_token;
870
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800871 wpa_msg(gas->wpa_s, MSG_INFO, GAS_QUERY_START "addr=" MACSTR
872 " dialog_token=%u freq=%d",
873 MAC2STR(query->addr), query->dialog_token, query->freq);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800874
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800875 if (radio_add_work(gas->wpa_s, freq, "gas-query", 0, gas_query_start_cb,
876 query) < 0) {
Dmitry Shmidt4ae50e62016-06-27 13:48:39 -0700877 query->req = NULL; /* caller will free this in error case */
Dmitry Shmidtfb79edc2014-01-10 10:45:54 -0800878 gas_query_free(query, 1);
879 return -1;
880 }
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800881
882 return dialog_token;
883}
Roshan Pius3a1667e2018-07-03 15:17:14 -0700884
885
886int gas_query_stop(struct gas_query *gas, u8 dialog_token)
887{
888 struct gas_query_pending *query;
889
890 dl_list_for_each(query, &gas->pending, struct gas_query_pending, list) {
891 if (query->dialog_token == dialog_token) {
892 if (!gas->work) {
893 /* The pending radio work has not yet been
894 * started, but the pending entry has a
895 * reference to the soon to be freed query.
896 * Need to remove that radio work now to avoid
897 * leaving behind a reference to freed memory.
898 */
899 radio_remove_pending_work(gas->wpa_s, query);
900 }
901 gas_query_done(gas, query, GAS_QUERY_STOPPED);
902 return 0;
903 }
904 }
905
906 return -1;
907}