gas_query.c revision 1.1 1 1.1 christos /*
2 1.1 christos * Generic advertisement service (GAS) query
3 1.1 christos * Copyright (c) 2009, Atheros Communications
4 1.1 christos * Copyright (c) 2011, Qualcomm Atheros
5 1.1 christos *
6 1.1 christos * This program is free software; you can redistribute it and/or modify
7 1.1 christos * it under the terms of the GNU General Public License version 2 as
8 1.1 christos * published by the Free Software Foundation.
9 1.1 christos *
10 1.1 christos * Alternatively, this software may be distributed under the terms of BSD
11 1.1 christos * license.
12 1.1 christos *
13 1.1 christos * See README and COPYING for more details.
14 1.1 christos */
15 1.1 christos
16 1.1 christos #include "includes.h"
17 1.1 christos
18 1.1 christos #include "common.h"
19 1.1 christos #include "utils/eloop.h"
20 1.1 christos #include "common/ieee802_11_defs.h"
21 1.1 christos #include "common/gas.h"
22 1.1 christos #include "wpa_supplicant_i.h"
23 1.1 christos #include "driver_i.h"
24 1.1 christos #include "offchannel.h"
25 1.1 christos #include "gas_query.h"
26 1.1 christos
27 1.1 christos
28 1.1 christos #define GAS_QUERY_TIMEOUT 5
29 1.1 christos
30 1.1 christos
31 1.1 christos struct gas_query_pending {
32 1.1 christos struct dl_list list;
33 1.1 christos u8 addr[ETH_ALEN];
34 1.1 christos u8 dialog_token;
35 1.1 christos u8 next_frag_id;
36 1.1 christos unsigned int wait_comeback:1;
37 1.1 christos unsigned int offchannel_tx_started:1;
38 1.1 christos int freq;
39 1.1 christos u16 status_code;
40 1.1 christos struct wpabuf *adv_proto;
41 1.1 christos struct wpabuf *resp;
42 1.1 christos void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
43 1.1 christos enum gas_query_result result,
44 1.1 christos const struct wpabuf *adv_proto,
45 1.1 christos const struct wpabuf *resp, u16 status_code);
46 1.1 christos void *ctx;
47 1.1 christos };
48 1.1 christos
49 1.1 christos struct gas_query {
50 1.1 christos struct wpa_supplicant *wpa_s;
51 1.1 christos struct dl_list pending; /* struct gas_query_pending */
52 1.1 christos };
53 1.1 christos
54 1.1 christos
55 1.1 christos static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx);
56 1.1 christos static void gas_query_timeout(void *eloop_data, void *user_ctx);
57 1.1 christos
58 1.1 christos
59 1.1 christos struct gas_query * gas_query_init(struct wpa_supplicant *wpa_s)
60 1.1 christos {
61 1.1 christos struct gas_query *gas;
62 1.1 christos
63 1.1 christos gas = os_zalloc(sizeof(*gas));
64 1.1 christos if (gas == NULL)
65 1.1 christos return NULL;
66 1.1 christos
67 1.1 christos gas->wpa_s = wpa_s;
68 1.1 christos dl_list_init(&gas->pending);
69 1.1 christos
70 1.1 christos return gas;
71 1.1 christos }
72 1.1 christos
73 1.1 christos
74 1.1 christos static void gas_query_done(struct gas_query *gas,
75 1.1 christos struct gas_query_pending *query,
76 1.1 christos enum gas_query_result result)
77 1.1 christos {
78 1.1 christos if (query->offchannel_tx_started)
79 1.1 christos offchannel_send_action_done(gas->wpa_s);
80 1.1 christos eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
81 1.1 christos eloop_cancel_timeout(gas_query_timeout, gas, query);
82 1.1 christos dl_list_del(&query->list);
83 1.1 christos query->cb(query->ctx, query->addr, query->dialog_token, result,
84 1.1 christos query->adv_proto, query->resp, query->status_code);
85 1.1 christos wpabuf_free(query->adv_proto);
86 1.1 christos wpabuf_free(query->resp);
87 1.1 christos os_free(query);
88 1.1 christos }
89 1.1 christos
90 1.1 christos
91 1.1 christos void gas_query_deinit(struct gas_query *gas)
92 1.1 christos {
93 1.1 christos struct gas_query_pending *query, *next;
94 1.1 christos
95 1.1 christos if (gas == NULL)
96 1.1 christos return;
97 1.1 christos
98 1.1 christos dl_list_for_each_safe(query, next, &gas->pending,
99 1.1 christos struct gas_query_pending, list)
100 1.1 christos gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT);
101 1.1 christos
102 1.1 christos os_free(gas);
103 1.1 christos }
104 1.1 christos
105 1.1 christos
106 1.1 christos static struct gas_query_pending *
107 1.1 christos gas_query_get_pending(struct gas_query *gas, const u8 *addr, u8 dialog_token)
108 1.1 christos {
109 1.1 christos struct gas_query_pending *q;
110 1.1 christos dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
111 1.1 christos if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
112 1.1 christos q->dialog_token == dialog_token)
113 1.1 christos return q;
114 1.1 christos }
115 1.1 christos return NULL;
116 1.1 christos }
117 1.1 christos
118 1.1 christos
119 1.1 christos static int gas_query_append(struct gas_query_pending *query, const u8 *data,
120 1.1 christos size_t len)
121 1.1 christos {
122 1.1 christos if (wpabuf_resize(&query->resp, len) < 0) {
123 1.1 christos wpa_printf(MSG_DEBUG, "GAS: No memory to store the response");
124 1.1 christos return -1;
125 1.1 christos }
126 1.1 christos wpabuf_put_data(query->resp, data, len);
127 1.1 christos return 0;
128 1.1 christos }
129 1.1 christos
130 1.1 christos
131 1.1 christos static int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query,
132 1.1 christos struct wpabuf *req)
133 1.1 christos {
134 1.1 christos int res;
135 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u "
136 1.1 christos "freq=%d", MAC2STR(query->addr),
137 1.1 christos (unsigned int) wpabuf_len(req), query->freq);
138 1.1 christos res = offchannel_send_action(gas->wpa_s, query->freq, query->addr,
139 1.1 christos gas->wpa_s->own_addr, query->addr,
140 1.1 christos wpabuf_head(req), wpabuf_len(req), 1000,
141 1.1 christos NULL, 0);
142 1.1 christos if (res == 0)
143 1.1 christos query->offchannel_tx_started = 1;
144 1.1 christos return res;
145 1.1 christos }
146 1.1 christos
147 1.1 christos
148 1.1 christos static void gas_query_tx_comeback_req(struct gas_query *gas,
149 1.1 christos struct gas_query_pending *query)
150 1.1 christos {
151 1.1 christos struct wpabuf *req;
152 1.1 christos
153 1.1 christos req = gas_build_comeback_req(query->dialog_token);
154 1.1 christos if (req == NULL) {
155 1.1 christos gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
156 1.1 christos return;
157 1.1 christos }
158 1.1 christos
159 1.1 christos if (gas_query_tx(gas, query, req) < 0) {
160 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
161 1.1 christos MACSTR, MAC2STR(query->addr));
162 1.1 christos gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
163 1.1 christos }
164 1.1 christos
165 1.1 christos wpabuf_free(req);
166 1.1 christos }
167 1.1 christos
168 1.1 christos
169 1.1 christos static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx)
170 1.1 christos {
171 1.1 christos struct gas_query *gas = eloop_data;
172 1.1 christos struct gas_query_pending *query = user_ctx;
173 1.1 christos
174 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR,
175 1.1 christos MAC2STR(query->addr));
176 1.1 christos gas_query_tx_comeback_req(gas, query);
177 1.1 christos }
178 1.1 christos
179 1.1 christos
180 1.1 christos static void gas_query_tx_comeback_req_delay(struct gas_query *gas,
181 1.1 christos struct gas_query_pending *query,
182 1.1 christos u16 comeback_delay)
183 1.1 christos {
184 1.1 christos unsigned int secs, usecs;
185 1.1 christos
186 1.1 christos secs = (comeback_delay * 1024) / 1000000;
187 1.1 christos usecs = comeback_delay * 1024 - secs * 1000000;
188 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR
189 1.1 christos " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs);
190 1.1 christos eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query);
191 1.1 christos eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout,
192 1.1 christos gas, query);
193 1.1 christos }
194 1.1 christos
195 1.1 christos
196 1.1 christos static void gas_query_rx_initial(struct gas_query *gas,
197 1.1 christos struct gas_query_pending *query,
198 1.1 christos const u8 *adv_proto, const u8 *resp,
199 1.1 christos size_t len, u16 comeback_delay)
200 1.1 christos {
201 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Received initial response from "
202 1.1 christos MACSTR " (dialog_token=%u comeback_delay=%u)",
203 1.1 christos MAC2STR(query->addr), query->dialog_token, comeback_delay);
204 1.1 christos
205 1.1 christos query->adv_proto = wpabuf_alloc_copy(adv_proto, 2 + adv_proto[1]);
206 1.1 christos if (query->adv_proto == NULL) {
207 1.1 christos gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
208 1.1 christos return;
209 1.1 christos }
210 1.1 christos
211 1.1 christos if (comeback_delay) {
212 1.1 christos query->wait_comeback = 1;
213 1.1 christos gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
214 1.1 christos return;
215 1.1 christos }
216 1.1 christos
217 1.1 christos /* Query was completed without comeback mechanism */
218 1.1 christos if (gas_query_append(query, resp, len) < 0) {
219 1.1 christos gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
220 1.1 christos return;
221 1.1 christos }
222 1.1 christos
223 1.1 christos gas_query_done(gas, query, GAS_QUERY_SUCCESS);
224 1.1 christos }
225 1.1 christos
226 1.1 christos
227 1.1 christos static void gas_query_rx_comeback(struct gas_query *gas,
228 1.1 christos struct gas_query_pending *query,
229 1.1 christos const u8 *adv_proto, const u8 *resp,
230 1.1 christos size_t len, u8 frag_id, u8 more_frags,
231 1.1 christos u16 comeback_delay)
232 1.1 christos {
233 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Received comeback response from "
234 1.1 christos MACSTR " (dialog_token=%u frag_id=%u more_frags=%u "
235 1.1 christos "comeback_delay=%u)",
236 1.1 christos MAC2STR(query->addr), query->dialog_token, frag_id,
237 1.1 christos more_frags, comeback_delay);
238 1.1 christos
239 1.1 christos if ((size_t) 2 + adv_proto[1] != wpabuf_len(query->adv_proto) ||
240 1.1 christos os_memcmp(adv_proto, wpabuf_head(query->adv_proto),
241 1.1 christos wpabuf_len(query->adv_proto)) != 0) {
242 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed "
243 1.1 christos "between initial and comeback response from "
244 1.1 christos MACSTR, MAC2STR(query->addr));
245 1.1 christos gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
246 1.1 christos return;
247 1.1 christos }
248 1.1 christos
249 1.1 christos if (comeback_delay) {
250 1.1 christos if (frag_id) {
251 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response "
252 1.1 christos "with non-zero frag_id and comeback_delay "
253 1.1 christos "from " MACSTR, MAC2STR(query->addr));
254 1.1 christos gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
255 1.1 christos return;
256 1.1 christos }
257 1.1 christos gas_query_tx_comeback_req_delay(gas, query, comeback_delay);
258 1.1 christos return;
259 1.1 christos }
260 1.1 christos
261 1.1 christos if (frag_id != query->next_frag_id) {
262 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response "
263 1.1 christos "from " MACSTR, MAC2STR(query->addr));
264 1.1 christos gas_query_done(gas, query, GAS_QUERY_PEER_ERROR);
265 1.1 christos return;
266 1.1 christos }
267 1.1 christos query->next_frag_id++;
268 1.1 christos
269 1.1 christos if (gas_query_append(query, resp, len) < 0) {
270 1.1 christos gas_query_done(gas, query, GAS_QUERY_INTERNAL_ERROR);
271 1.1 christos return;
272 1.1 christos }
273 1.1 christos
274 1.1 christos if (more_frags) {
275 1.1 christos gas_query_tx_comeback_req(gas, query);
276 1.1 christos return;
277 1.1 christos }
278 1.1 christos
279 1.1 christos gas_query_done(gas, query, GAS_QUERY_SUCCESS);
280 1.1 christos }
281 1.1 christos
282 1.1 christos
283 1.1 christos int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa,
284 1.1 christos const u8 *bssid, const u8 *data, size_t len, int freq)
285 1.1 christos {
286 1.1 christos struct gas_query_pending *query;
287 1.1 christos u8 action, dialog_token, frag_id = 0, more_frags = 0;
288 1.1 christos u16 comeback_delay, resp_len;
289 1.1 christos const u8 *pos, *adv_proto;
290 1.1 christos
291 1.1 christos if (gas == NULL || len < 4)
292 1.1 christos return -1;
293 1.1 christos
294 1.1 christos pos = data;
295 1.1 christos action = *pos++;
296 1.1 christos dialog_token = *pos++;
297 1.1 christos
298 1.1 christos if (action != WLAN_PA_GAS_INITIAL_RESP &&
299 1.1 christos action != WLAN_PA_GAS_COMEBACK_RESP)
300 1.1 christos return -1; /* Not a GAS response */
301 1.1 christos
302 1.1 christos query = gas_query_get_pending(gas, sa, dialog_token);
303 1.1 christos if (query == NULL) {
304 1.1 christos wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR
305 1.1 christos " dialog token %u", MAC2STR(sa), dialog_token);
306 1.1 christos return -1;
307 1.1 christos }
308 1.1 christos
309 1.1 christos if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) {
310 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from "
311 1.1 christos MACSTR " dialog token %u when waiting for comeback "
312 1.1 christos "response", MAC2STR(sa), dialog_token);
313 1.1 christos return 0;
314 1.1 christos }
315 1.1 christos
316 1.1 christos if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) {
317 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from "
318 1.1 christos MACSTR " dialog token %u when waiting for initial "
319 1.1 christos "response", MAC2STR(sa), dialog_token);
320 1.1 christos return 0;
321 1.1 christos }
322 1.1 christos
323 1.1 christos query->status_code = WPA_GET_LE16(pos);
324 1.1 christos pos += 2;
325 1.1 christos
326 1.1 christos if (query->status_code != WLAN_STATUS_SUCCESS) {
327 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token "
328 1.1 christos "%u failed - status code %u",
329 1.1 christos MAC2STR(sa), dialog_token, query->status_code);
330 1.1 christos gas_query_done(gas, query, GAS_QUERY_FAILURE);
331 1.1 christos return 0;
332 1.1 christos }
333 1.1 christos
334 1.1 christos if (action == WLAN_PA_GAS_COMEBACK_RESP) {
335 1.1 christos if (pos + 1 > data + len)
336 1.1 christos return 0;
337 1.1 christos frag_id = *pos & 0x7f;
338 1.1 christos more_frags = (*pos & 0x80) >> 7;
339 1.1 christos pos++;
340 1.1 christos }
341 1.1 christos
342 1.1 christos /* Comeback Delay */
343 1.1 christos if (pos + 2 > data + len)
344 1.1 christos return 0;
345 1.1 christos comeback_delay = WPA_GET_LE16(pos);
346 1.1 christos pos += 2;
347 1.1 christos
348 1.1 christos /* Advertisement Protocol element */
349 1.1 christos if (pos + 2 > data + len || pos + 2 + pos[1] > data + len) {
350 1.1 christos wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement "
351 1.1 christos "Protocol element in the response from " MACSTR,
352 1.1 christos MAC2STR(sa));
353 1.1 christos return 0;
354 1.1 christos }
355 1.1 christos
356 1.1 christos if (*pos != WLAN_EID_ADV_PROTO) {
357 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement "
358 1.1 christos "Protocol element ID %u in response from " MACSTR,
359 1.1 christos *pos, MAC2STR(sa));
360 1.1 christos return 0;
361 1.1 christos }
362 1.1 christos
363 1.1 christos adv_proto = pos;
364 1.1 christos pos += 2 + pos[1];
365 1.1 christos
366 1.1 christos /* Query Response Length */
367 1.1 christos if (pos + 2 > data + len) {
368 1.1 christos wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length");
369 1.1 christos return 0;
370 1.1 christos }
371 1.1 christos resp_len = WPA_GET_LE16(pos);
372 1.1 christos pos += 2;
373 1.1 christos
374 1.1 christos if (pos + resp_len > data + len) {
375 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in "
376 1.1 christos "response from " MACSTR, MAC2STR(sa));
377 1.1 christos return 0;
378 1.1 christos }
379 1.1 christos
380 1.1 christos if (pos + resp_len < data + len) {
381 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data "
382 1.1 christos "after Query Response from " MACSTR,
383 1.1 christos (unsigned int) (data + len - pos - resp_len),
384 1.1 christos MAC2STR(sa));
385 1.1 christos }
386 1.1 christos
387 1.1 christos if (action == WLAN_PA_GAS_COMEBACK_RESP)
388 1.1 christos gas_query_rx_comeback(gas, query, adv_proto, pos, resp_len,
389 1.1 christos frag_id, more_frags, comeback_delay);
390 1.1 christos else
391 1.1 christos gas_query_rx_initial(gas, query, adv_proto, pos, resp_len,
392 1.1 christos comeback_delay);
393 1.1 christos
394 1.1 christos return 0;
395 1.1 christos }
396 1.1 christos
397 1.1 christos
398 1.1 christos static void gas_query_timeout(void *eloop_data, void *user_ctx)
399 1.1 christos {
400 1.1 christos struct gas_query *gas = eloop_data;
401 1.1 christos struct gas_query_pending *query = user_ctx;
402 1.1 christos
403 1.1 christos wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR,
404 1.1 christos MAC2STR(query->addr));
405 1.1 christos gas_query_done(gas, query, GAS_QUERY_TIMEOUT);
406 1.1 christos }
407 1.1 christos
408 1.1 christos
409 1.1 christos static int gas_query_dialog_token_available(struct gas_query *gas,
410 1.1 christos const u8 *dst, u8 dialog_token)
411 1.1 christos {
412 1.1 christos struct gas_query_pending *q;
413 1.1 christos dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
414 1.1 christos if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
415 1.1 christos dialog_token == q->dialog_token)
416 1.1 christos return 0;
417 1.1 christos }
418 1.1 christos
419 1.1 christos return 1;
420 1.1 christos }
421 1.1 christos
422 1.1 christos
423 1.1 christos int gas_query_req(struct gas_query *gas, const u8 *dst, int freq,
424 1.1 christos struct wpabuf *req,
425 1.1 christos void (*cb)(void *ctx, const u8 *dst, u8 dialog_token,
426 1.1 christos enum gas_query_result result,
427 1.1 christos const struct wpabuf *adv_proto,
428 1.1 christos const struct wpabuf *resp, u16 status_code),
429 1.1 christos void *ctx)
430 1.1 christos {
431 1.1 christos struct gas_query_pending *query;
432 1.1 christos int dialog_token;
433 1.1 christos
434 1.1 christos if (wpabuf_len(req) < 3)
435 1.1 christos return -1;
436 1.1 christos
437 1.1 christos for (dialog_token = 0; dialog_token < 256; dialog_token++) {
438 1.1 christos if (gas_query_dialog_token_available(gas, dst, dialog_token))
439 1.1 christos break;
440 1.1 christos }
441 1.1 christos if (dialog_token == 256)
442 1.1 christos return -1; /* Too many pending queries */
443 1.1 christos
444 1.1 christos query = os_zalloc(sizeof(*query));
445 1.1 christos if (query == NULL)
446 1.1 christos return -1;
447 1.1 christos
448 1.1 christos os_memcpy(query->addr, dst, ETH_ALEN);
449 1.1 christos query->dialog_token = dialog_token;
450 1.1 christos query->freq = freq;
451 1.1 christos query->cb = cb;
452 1.1 christos query->ctx = ctx;
453 1.1 christos dl_list_add(&gas->pending, &query->list);
454 1.1 christos
455 1.1 christos *(wpabuf_mhead_u8(req) + 2) = dialog_token;
456 1.1 christos
457 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Starting request for " MACSTR
458 1.1 christos " dialog_token %u", MAC2STR(dst), dialog_token);
459 1.1 christos if (gas_query_tx(gas, query, req) < 0) {
460 1.1 christos wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to "
461 1.1 christos MACSTR, MAC2STR(query->addr));
462 1.1 christos os_free(query);
463 1.1 christos return -1;
464 1.1 christos }
465 1.1 christos
466 1.1 christos eloop_register_timeout(GAS_QUERY_TIMEOUT, 0, gas_query_timeout,
467 1.1 christos gas, query);
468 1.1 christos
469 1.1 christos return dialog_token;
470 1.1 christos }
471 1.1 christos
472 1.1 christos
473 1.1 christos void gas_query_cancel(struct gas_query *gas, const u8 *dst, u8 dialog_token)
474 1.1 christos {
475 1.1 christos struct gas_query_pending *query;
476 1.1 christos
477 1.1 christos query = gas_query_get_pending(gas, dst, dialog_token);
478 1.1 christos if (query)
479 1.1 christos gas_query_done(gas, query, GAS_QUERY_CANCELLED);
480 1.1 christos
481 1.1 christos }
482